22
ReactDOM = require('react-dom');
23
});
24
25
+ // Sets a value in a way that React doesn't see,
26
+ // so that a subsequent "change" event will trigger the event handler.
27
+ const setUntrackedValue = Object.getOwnPropertyDescriptor(
28
+ HTMLInputElement.prototype,
29
+ 'value',
30
+ ).set;
31
+ const setUntrackedChecked = Object.getOwnPropertyDescriptor(
32
+ HTMLInputElement.prototype,
33
+ 'checked',
34
+ ).set;
35
+
36
describe('setValueForProperty', () => {
37
it('should set values as properties by default', () => {
38
const container = document.createElement('div');
291
expect(syntheticClickEvent.nativeEvent).toBe(nativeClickEvent);
292
});
293
294
+ // @gate enableCustomElementPropertySupport
295
+ it('custom elements should have working onChange event listeners', () => {
296
+ let reactChangeEvent = null;
297
+ const eventHandler = jest.fn(event => (reactChangeEvent = event));
298
+ const container = document.createElement('div');
299
+ document.body.appendChild(container);
300
+ ReactDOM.render(<my-custom-element onChange={eventHandler} />, container);
301
+ const customElement = container.querySelector('my-custom-element');
302
+ let expectedHandlerCallCount = 0;
303
+
304
+ const changeEvent = new Event('change', {bubbles: true});
305
+ customElement.dispatchEvent(changeEvent);
306
+ expectedHandlerCallCount++;
307
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
308
+ expect(reactChangeEvent.nativeEvent).toBe(changeEvent);
309
+
310
+ // Also make sure that removing and re-adding the event listener works
311
+ ReactDOM.render(<my-custom-element />, container);
312
+ customElement.dispatchEvent(new Event('change', {bubbles: true}));
313
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
314
+ ReactDOM.render(<my-custom-element onChange={eventHandler} />, container);
315
+ customElement.dispatchEvent(new Event('change', {bubbles: true}));
316
+ expectedHandlerCallCount++;
317
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
318
+ });
319
+
320
+ it('custom elements should have working onInput event listeners', () => {
321
+ let reactInputEvent = null;
322
+ const eventHandler = jest.fn(event => (reactInputEvent = event));
323
+ const container = document.createElement('div');
324
+ document.body.appendChild(container);
325
+ ReactDOM.render(<my-custom-element onInput={eventHandler} />, container);
326
+ const customElement = container.querySelector('my-custom-element');
327
+ let expectedHandlerCallCount = 0;
328
+
329
+ const inputEvent = new Event('input', {bubbles: true});
330
+ customElement.dispatchEvent(inputEvent);
331
+ expectedHandlerCallCount++;
332
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
333
+ expect(reactInputEvent.nativeEvent).toBe(inputEvent);
334
+
335
+ // Also make sure that removing and re-adding the event listener works
336
+ ReactDOM.render(<my-custom-element />, container);
337
+ customElement.dispatchEvent(new Event('input', {bubbles: true}));
338
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
339
+ ReactDOM.render(<my-custom-element onInput={eventHandler} />, container);
340
+ customElement.dispatchEvent(new Event('input', {bubbles: true}));
341
+ expectedHandlerCallCount++;
342
+ expect(eventHandler).toHaveBeenCalledTimes(expectedHandlerCallCount);
343
+ });
344
+
345
+ // @gate enableCustomElementPropertySupport
346
+ it('custom elements should have separate onInput and onChange handling', () => {
347
+ const container = document.createElement('div');
348
+ document.body.appendChild(container);
349
+ const inputEventHandler = jest.fn();
350
+ const changeEventHandler = jest.fn();
351
+ ReactDOM.render(
352
+ <my-custom-element
353
+ onInput={inputEventHandler}
354
+ onChange={changeEventHandler}
355
+ />,
356
+ container,
357
+ );
358
+ const customElement = container.querySelector('my-custom-element');
359
+
360
+ customElement.dispatchEvent(new Event('input', {bubbles: true}));
361
+ expect(inputEventHandler).toHaveBeenCalledTimes(1);
362
+ expect(changeEventHandler).toHaveBeenCalledTimes(0);
363
+
364
+ customElement.dispatchEvent(new Event('change', {bubbles: true}));
365
+ expect(inputEventHandler).toHaveBeenCalledTimes(1);
366
+ expect(changeEventHandler).toHaveBeenCalledTimes(1);
367
+ });
368
+
369
+ // @gate enableCustomElementPropertySupport
370
+ it('custom elements should be able to remove and re-add custom event listeners', () => {
371
+ const container = document.createElement('div');
372
+ document.body.appendChild(container);
373
+ const eventHandler = jest.fn();
374
+ ReactDOM.render(
375
+ <my-custom-element oncustomevent={eventHandler} />,
376
+ container,
377
+ );
378
+
379
+ const customElement = container.querySelector('my-custom-element');
380
+ customElement.dispatchEvent(new Event('customevent'));
381
+ expect(eventHandler).toHaveBeenCalledTimes(1);
382
+
383
+ ReactDOM.render(<my-custom-element />, container);
384
+ customElement.dispatchEvent(new Event('customevent'));
385
+ expect(eventHandler).toHaveBeenCalledTimes(1);
386
+
387
+ ReactDOM.render(
388
+ <my-custom-element oncustomevent={eventHandler} />,
389
+ container,
390
+ );
391
+ customElement.dispatchEvent(new Event('customevent'));
392
+ expect(eventHandler).toHaveBeenCalledTimes(2);
393
+ });
394
+
395
+ it('<input is=...> should have the same onChange/onInput/onClick behavior as <input>', () => {
396
+ const container = document.createElement('div');
397
+ document.body.appendChild(container);
398
+ const regularOnInputHandler = jest.fn();
399
+ const regularOnChangeHandler = jest.fn();
400
+ const regularOnClickHandler = jest.fn();
401
+ const customOnInputHandler = jest.fn();
402
+ const customOnChangeHandler = jest.fn();
403
+ const customOnClickHandler = jest.fn();
404
+ function clearMocks() {
405
+ regularOnInputHandler.mockClear();
406
+ regularOnChangeHandler.mockClear();
407
+ regularOnClickHandler.mockClear();
408
+ customOnInputHandler.mockClear();
409
+ customOnChangeHandler.mockClear();
410
+ customOnClickHandler.mockClear();
411
+ }
412
+ ReactDOM.render(
413
+ <div>
414
+ <input
415
+ onInput={regularOnInputHandler}
416
+ onChange={regularOnChangeHandler}
417
+ onClick={regularOnClickHandler}
418
+ />
419
+ <input
420
+ is="my-custom-element"
421
+ onInput={customOnInputHandler}
422
+ onChange={customOnChangeHandler}
423
+ onClick={customOnClickHandler}
424
+ />
425
+ </div>,
426
+ container,
427
+ );
428
+
429
+ const regularInput = container.querySelector(
430
+ 'input:not([is=my-custom-element])',
431
+ );
432
+ const customInput = container.querySelector(
433
+ 'input[is=my-custom-element]',
434
+ );
435
+ expect(regularInput).not.toBe(customInput);
436
+
437
+ // Typing should trigger onInput and onChange for both kinds of inputs.
438
+ clearMocks();
439
+ setUntrackedValue.call(regularInput, 'hello');
440
+ regularInput.dispatchEvent(new Event('input', {bubbles: true}));
441
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
442
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
443
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
444
+ setUntrackedValue.call(customInput, 'hello');
445
+ customInput.dispatchEvent(new Event('input', {bubbles: true}));
446
+ expect(customOnInputHandler).toHaveBeenCalledTimes(1);
447
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
448
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
449
+
450
+ // The native change event itself does not produce extra React events.
451
+ clearMocks();
452
+ regularInput.dispatchEvent(new Event('change', {bubbles: true}));
453
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
454
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
455
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
456
+ customInput.dispatchEvent(new Event('change', {bubbles: true}));
457
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
458
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
459
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
460
+
461
+ // The click event is handled by both inputs.
462
+ clearMocks();
463
+ regularInput.dispatchEvent(new Event('click', {bubbles: true}));
464
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
465
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
466
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
467
+ customInput.dispatchEvent(new Event('click', {bubbles: true}));
468
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
469
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
470
+ expect(customOnClickHandler).toHaveBeenCalledTimes(1);
471
+
472
+ // Typing again should trigger onInput and onChange for both kinds of inputs.
473
+ clearMocks();
474
+ setUntrackedValue.call(regularInput, 'goodbye');
475
+ regularInput.dispatchEvent(new Event('input', {bubbles: true}));
476
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
477
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
478
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
479
+ setUntrackedValue.call(customInput, 'goodbye');
480
+ customInput.dispatchEvent(new Event('input', {bubbles: true}));
481
+ expect(customOnInputHandler).toHaveBeenCalledTimes(1);
482
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
483
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
484
+ });
485
+
486
+ it('<input type=radio is=...> should have the same onChange/onInput/onClick behavior as <input type=radio>', () => {
487
+ const container = document.createElement('div');
488
+ document.body.appendChild(container);
489
+ const regularOnInputHandler = jest.fn();
490
+ const regularOnChangeHandler = jest.fn();
491
+ const regularOnClickHandler = jest.fn();
492
+ const customOnInputHandler = jest.fn();
493
+ const customOnChangeHandler = jest.fn();
494
+ const customOnClickHandler = jest.fn();
495
+ function clearMocks() {
496
+ regularOnInputHandler.mockClear();
497
+ regularOnChangeHandler.mockClear();
498
+ regularOnClickHandler.mockClear();
499
+ customOnInputHandler.mockClear();
500
+ customOnChangeHandler.mockClear();
501
+ customOnClickHandler.mockClear();
502
+ }
503
+ ReactDOM.render(
504
+ <div>
505
+ <input
506
+ type="radio"
507
+ onInput={regularOnInputHandler}
508
+ onChange={regularOnChangeHandler}
509
+ onClick={regularOnClickHandler}
510
+ />
511
+ <input
512
+ is="my-custom-element"
513
+ type="radio"
514
+ onInput={customOnInputHandler}
515
+ onChange={customOnChangeHandler}
516
+ onClick={customOnClickHandler}
517
+ />
518
+ </div>,
519
+ container,
520
+ );
521
+
522
+ const regularInput = container.querySelector(
523
+ 'input:not([is=my-custom-element])',
524
+ );
525
+ const customInput = container.querySelector(
526
+ 'input[is=my-custom-element]',
527
+ );
528
+ expect(regularInput).not.toBe(customInput);
529
+
530
+ // Clicking should trigger onClick and onChange on both inputs.
531
+ clearMocks();
532
+ setUntrackedChecked.call(regularInput, true);
533
+ regularInput.dispatchEvent(new Event('click', {bubbles: true}));
534
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
535
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
536
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
537
+ setUntrackedChecked.call(customInput, true);
538
+ customInput.dispatchEvent(new Event('click', {bubbles: true}));
539
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
540
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
541
+ expect(customOnClickHandler).toHaveBeenCalledTimes(1);
542
+
543
+ // The native input event only produces a React onInput event.
544
+ clearMocks();
545
+ regularInput.dispatchEvent(new Event('input', {bubbles: true}));
546
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
547
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
548
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
549
+ customInput.dispatchEvent(new Event('input', {bubbles: true}));
550
+ expect(customOnInputHandler).toHaveBeenCalledTimes(1);
551
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
552
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
553
+
554
+ // Clicking again should trigger onClick and onChange on both inputs.
555
+ clearMocks();
556
+ setUntrackedChecked.call(regularInput, false);
557
+ regularInput.dispatchEvent(new Event('click', {bubbles: true}));
558
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
559
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
560
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
561
+ setUntrackedChecked.call(customInput, false);
562
+ customInput.dispatchEvent(new Event('click', {bubbles: true}));
563
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
564
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
565
+ expect(customOnClickHandler).toHaveBeenCalledTimes(1);
566
+ });
567
+
568
+ it('<select is=...> should have the same onChange/onInput/onClick behavior as <select>', () => {
569
+ const container = document.createElement('div');
570
+ document.body.appendChild(container);
571
+ const regularOnInputHandler = jest.fn();
572
+ const regularOnChangeHandler = jest.fn();
573
+ const regularOnClickHandler = jest.fn();
574
+ const customOnInputHandler = jest.fn();
575
+ const customOnChangeHandler = jest.fn();
576
+ const customOnClickHandler = jest.fn();
577
+ function clearMocks() {
578
+ regularOnInputHandler.mockClear();
579
+ regularOnChangeHandler.mockClear();
580
+ regularOnClickHandler.mockClear();
581
+ customOnInputHandler.mockClear();
582
+ customOnChangeHandler.mockClear();
583
+ customOnClickHandler.mockClear();
584
+ }
585
+ ReactDOM.render(
586
+ <div>
587
+ <select
588
+ onInput={regularOnInputHandler}
589
+ onChange={regularOnChangeHandler}
590
+ onClick={regularOnClickHandler}
591
+ />
592
+ <select
593
+ is="my-custom-element"
594
+ onInput={customOnInputHandler}
595
+ onChange={customOnChangeHandler}
596
+ onClick={customOnClickHandler}
597
+ />
598
+ </div>,
599
+ container,
600
+ );
601
+
602
+ const regularSelect = container.querySelector(
603
+ 'select:not([is=my-custom-element])',
604
+ );
605
+ const customSelect = container.querySelector(
606
+ 'select[is=my-custom-element]',
607
+ );
608
+ expect(regularSelect).not.toBe(customSelect);
609
+
610
+ // Clicking should only trigger onClick on both inputs.
611
+ clearMocks();
612
+ regularSelect.dispatchEvent(new Event('click', {bubbles: true}));
613
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
614
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
615
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(1);
616
+ customSelect.dispatchEvent(new Event('click', {bubbles: true}));
617
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
618
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
619
+ expect(customOnClickHandler).toHaveBeenCalledTimes(1);
620
+
621
+ // Native input event should only trigger onInput on both inputs.
622
+ clearMocks();
623
+ regularSelect.dispatchEvent(new Event('input', {bubbles: true}));
624
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(1);
625
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(0);
626
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
627
+ customSelect.dispatchEvent(new Event('input', {bubbles: true}));
628
+ expect(customOnInputHandler).toHaveBeenCalledTimes(1);
629
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(0);
630
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
631
+
632
+ // Native change event should trigger onChange.
633
+ clearMocks();
634
+ regularSelect.dispatchEvent(new Event('change', {bubbles: true}));
635
+ expect(regularOnInputHandler).toHaveBeenCalledTimes(0);
636
+ expect(regularOnChangeHandler).toHaveBeenCalledTimes(1);
637
+ expect(regularOnClickHandler).toHaveBeenCalledTimes(0);
638
+ customSelect.dispatchEvent(new Event('change', {bubbles: true}));
639
+ expect(customOnInputHandler).toHaveBeenCalledTimes(0);
640
+ expect(customOnChangeHandler).toHaveBeenCalledTimes(1);
641
+ expect(customOnClickHandler).toHaveBeenCalledTimes(0);
642
+ });
643
+
644
+ // @gate enableCustomElementPropertySupport
645
+ it('onChange/onInput/onClick on div with various types of children', () => {
646
+ const container = document.createElement('div');
647
+ document.body.appendChild(container);
648
+ const onChangeHandler = jest.fn();
649
+ const onInputHandler = jest.fn();
650
+ const onClickHandler = jest.fn();
651
+ function clearMocks() {
652
+ onChangeHandler.mockClear();
653
+ onInputHandler.mockClear();
654
+ onClickHandler.mockClear();
655
+ }
656
+ ReactDOM.render(
657
+ <div
658
+ onChange={onChangeHandler}
659
+ onInput={onInputHandler}
660
+ onClick={onClickHandler}>
661
+ <my-custom-element />
662
+ <input />
663
+ <input is="my-custom-element" />
664
+ </div>,
665
+ container,
666
+ );
667
+ const customElement = container.querySelector('my-custom-element');
668
+ const regularInput = container.querySelector(
669
+ 'input:not([is="my-custom-element"])',
670
+ );
671
+ const customInput = container.querySelector(
672
+ 'input[is="my-custom-element"]',
673
+ );
674
+ expect(regularInput).not.toBe(customInput);
675
+
676
+ // Custom element has no special logic for input/change.
677
+ clearMocks();
678
+ customElement.dispatchEvent(new Event('input', {bubbles: true}));
679
+ expect(onChangeHandler).toBeCalledTimes(0);
680
+ expect(onInputHandler).toBeCalledTimes(1);
681
+ expect(onClickHandler).toBeCalledTimes(0);
682
+ customElement.dispatchEvent(new Event('change', {bubbles: true}));
683
+ expect(onChangeHandler).toBeCalledTimes(1);
684
+ expect(onInputHandler).toBeCalledTimes(1);
685
+ expect(onClickHandler).toBeCalledTimes(0);
686
+ customElement.dispatchEvent(new Event('click', {bubbles: true}));
687
+ expect(onChangeHandler).toBeCalledTimes(1);
688
+ expect(onInputHandler).toBeCalledTimes(1);
689
+ expect(onClickHandler).toBeCalledTimes(1);
690
+
691
+ // Regular input treats browser input as onChange.
692
+ clearMocks();
693
+ setUntrackedValue.call(regularInput, 'hello');
694
+ regularInput.dispatchEvent(new Event('input', {bubbles: true}));
695
+ expect(onChangeHandler).toBeCalledTimes(1);
696
+ expect(onInputHandler).toBeCalledTimes(1);
697
+ expect(onClickHandler).toBeCalledTimes(0);
698
+ regularInput.dispatchEvent(new Event('change', {bubbles: true}));
699
+ expect(onChangeHandler).toBeCalledTimes(1);
700
+ expect(onInputHandler).toBeCalledTimes(1);
701
+ expect(onClickHandler).toBeCalledTimes(0);
702
+ regularInput.dispatchEvent(new Event('click', {bubbles: true}));
703
+ expect(onChangeHandler).toBeCalledTimes(1);
704
+ expect(onInputHandler).toBeCalledTimes(1);
705
+ expect(onClickHandler).toBeCalledTimes(1);
706
+
707
+ // Custom input treats browser input as onChange.
708
+ clearMocks();
709
+ setUntrackedValue.call(customInput, 'hello');
710
+ customInput.dispatchEvent(new Event('input', {bubbles: true}));
711
+ expect(onChangeHandler).toBeCalledTimes(1);
712
+ expect(onInputHandler).toBeCalledTimes(1);
713
+ expect(onClickHandler).toBeCalledTimes(0);
714
+ customInput.dispatchEvent(new Event('change', {bubbles: true}));
715
+ expect(onChangeHandler).toBeCalledTimes(1);
716
+ expect(onInputHandler).toBeCalledTimes(1);
717
+ expect(onClickHandler).toBeCalledTimes(0);
718
+ customInput.dispatchEvent(new Event('click', {bubbles: true}));
719
+ expect(onChangeHandler).toBeCalledTimes(1);
720
+ expect(onInputHandler).toBeCalledTimes(1);
721
+ expect(onClickHandler).toBeCalledTimes(1);
722
+ });
723
+
724
+ it('custom element onChange/onInput/onClick with event target input child', () => {
725
+ const container = document.createElement('div');
726
+ document.body.appendChild(container);
727
+ const onChangeHandler = jest.fn();
728
+ const onInputHandler = jest.fn();
729
+ const onClickHandler = jest.fn();
730
+ ReactDOM.render(
731
+ <my-custom-element
732
+ onChange={onChangeHandler}
733
+ onInput={onInputHandler}
734
+ onClick={onClickHandler}>
735
+ <input />
736
+ </my-custom-element>,
737
+ container,
738
+ );
739
+
740
+ const input = container.querySelector('input');
741
+ setUntrackedValue.call(input, 'hello');
742
+ input.dispatchEvent(new Event('input', {bubbles: true}));
743
+ // Simulated onChange from the child's input event
744
+ // bubbles to the parent custom element.
745
+ expect(onChangeHandler).toBeCalledTimes(1);
746
+ expect(onInputHandler).toBeCalledTimes(1);
747
+ expect(onClickHandler).toBeCalledTimes(0);
748
+ // Consequently, the native change event is ignored.
749
+ input.dispatchEvent(new Event('change', {bubbles: true}));
750
+ expect(onChangeHandler).toBeCalledTimes(1);
751
+ expect(onInputHandler).toBeCalledTimes(1);
752
+ expect(onClickHandler).toBeCalledTimes(0);
753
+ input.dispatchEvent(new Event('click', {bubbles: true}));
754
+ expect(onChangeHandler).toBeCalledTimes(1);
755
+ expect(onInputHandler).toBeCalledTimes(1);
756
+ expect(onClickHandler).toBeCalledTimes(1);
757
+ });
758
+
759
+ it('custom element onChange/onInput/onClick with event target div child', () => {
760
+ const container = document.createElement('div');
761
+ document.body.appendChild(container);
762
+ const onChangeHandler = jest.fn();
763
+ const onInputHandler = jest.fn();
764
+ const onClickHandler = jest.fn();
765
+ ReactDOM.render(
766
+ <my-custom-element
767
+ onChange={onChangeHandler}
768
+ onInput={onInputHandler}
769
+ onClick={onClickHandler}>
770
+ <div />
771
+ </my-custom-element>,
772
+ container,
773
+ );
774
+
775
+ const div = container.querySelector('div');
776
+ div.dispatchEvent(new Event('input', {bubbles: true}));
777
+ expect(onChangeHandler).toBeCalledTimes(0);
778
+ expect(onInputHandler).toBeCalledTimes(1);
779
+ expect(onClickHandler).toBeCalledTimes(0);
780
+
781
+ div.dispatchEvent(new Event('change', {bubbles: true}));
782
+ // React always ignores change event invoked on non-custom and non-input targets.
783
+ // So change event emitted on a div does not propagate upwards.
784
+ expect(onChangeHandler).toBeCalledTimes(0);
785
+ expect(onInputHandler).toBeCalledTimes(1);
786
+ expect(onClickHandler).toBeCalledTimes(0);
787
+
788
+ div.dispatchEvent(new Event('click', {bubbles: true}));
789
+ expect(onChangeHandler).toBeCalledTimes(0);
790
+ expect(onInputHandler).toBeCalledTimes(1);
791
+ expect(onClickHandler).toBeCalledTimes(1);
792
+ });
793
+
794
+ it('div onChange/onInput/onClick with event target div child', () => {
795
+ const container = document.createElement('div');
796
+ document.body.appendChild(container);
797
+ const onChangeHandler = jest.fn();
798
+ const onInputHandler = jest.fn();
799
+ const onClickHandler = jest.fn();
800
+ ReactDOM.render(
801
+ <div
802
+ onChange={onChangeHandler}
803
+ onInput={onInputHandler}
804
+ onClick={onClickHandler}>
805
+ <div />
806
+ </div>,
807
+ container,
808
+ );
809
+
810
+ const div = container.querySelector('div > div');
811
+ div.dispatchEvent(new Event('input', {bubbles: true}));
812
+ expect(onChangeHandler).toBeCalledTimes(0);
813
+ expect(onInputHandler).toBeCalledTimes(1);
814
+ expect(onClickHandler).toBeCalledTimes(0);
815
+
816
+ div.dispatchEvent(new Event('change', {bubbles: true}));
817
+ // React always ignores change event invoked on non-custom and non-input targets.
818
+ // So change event emitted on a div does not propagate upwards.
819
+ expect(onChangeHandler).toBeCalledTimes(0);
820
+ expect(onInputHandler).toBeCalledTimes(1);
821
+ expect(onClickHandler).toBeCalledTimes(0);
822
+
823
+ div.dispatchEvent(new Event('click', {bubbles: true}));
824
+ expect(onChangeHandler).toBeCalledTimes(0);
825
+ expect(onInputHandler).toBeCalledTimes(1);
826
+ expect(onClickHandler).toBeCalledTimes(1);
827
+ });
828
+
829
+ // @gate enableCustomElementPropertySupport
830
+ it('custom element onChange/onInput/onClick with event target custom element child', () => {
831
+ const container = document.createElement('div');
832
+ document.body.appendChild(container);
833
+ const onChangeHandler = jest.fn();
834
+ const onInputHandler = jest.fn();
835
+ const onClickHandler = jest.fn();
836
+ ReactDOM.render(
837
+ <my-custom-element
838
+ onChange={onChangeHandler}
839
+ onInput={onInputHandler}
840
+ onClick={onClickHandler}>
841
+ <other-custom-element />
842
+ </my-custom-element>,
843
+ container,
844
+ );
845
+
846
+ const customChild = container.querySelector('other-custom-element');
847
+ customChild.dispatchEvent(new Event('input', {bubbles: true}));
848
+ // There is no simulated onChange, only raw onInput is dispatched.
849
+ expect(onChangeHandler).toBeCalledTimes(0);
850
+ expect(onInputHandler).toBeCalledTimes(1);
851
+ expect(onClickHandler).toBeCalledTimes(0);
852
+ // The native change event propagates to the parent as onChange.
853
+ customChild.dispatchEvent(new Event('change', {bubbles: true}));
854
+ expect(onChangeHandler).toBeCalledTimes(1);
855
+ expect(onInputHandler).toBeCalledTimes(1);
856
+ expect(onClickHandler).toBeCalledTimes(0);
857
+ customChild.dispatchEvent(new Event('click', {bubbles: true}));
858
+ expect(onChangeHandler).toBeCalledTimes(1);
859
+ expect(onInputHandler).toBeCalledTimes(1);
860
+ expect(onClickHandler).toBeCalledTimes(1);
861
+ });
862
+
863
// @gate enableCustomElementPropertySupport
864
it('custom elements should allow custom events with capture event listeners', () => {
865
const oncustomeventCapture = jest.fn();