fireInputEvent.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.fireInputEvent = fireInputEvent;
  6. var _dom = require("@testing-library/dom");
  7. var _utils = require("../../utils");
  8. function fireInputEvent(element, {
  9. newValue,
  10. newSelectionStart,
  11. eventOverrides
  12. }) {
  13. // apply the changes before firing the input event, so that input handlers can access the altered dom and selection
  14. if ((0, _utils.isContentEditable)(element)) {
  15. applyNative(element, 'textContent', newValue);
  16. } else
  17. /* istanbul ignore else */
  18. if ((0, _utils.isElementType)(element, ['input', 'textarea'])) {
  19. applyNative(element, 'value', newValue);
  20. } else {
  21. // TODO: properly type guard
  22. throw new Error('Invalid Element');
  23. }
  24. setSelectionRangeAfterInput(element, newSelectionStart);
  25. _dom.fireEvent.input(element, { ...eventOverrides
  26. });
  27. setSelectionRangeAfterInputHandler(element, newValue, newSelectionStart);
  28. }
  29. function setSelectionRangeAfterInput(element, newSelectionStart) {
  30. (0, _utils.setSelectionRange)(element, newSelectionStart, newSelectionStart);
  31. }
  32. function setSelectionRangeAfterInputHandler(element, newValue, newSelectionStart) {
  33. const value = (0, _utils.getValue)(element); // don't apply this workaround on elements that don't necessarily report the visible value - e.g. number
  34. // TODO: this could probably be only applied when there is keyboardState.carryValue
  35. const isUnreliableValue = value === '' && (0, _utils.hasUnreliableEmptyValue)(element);
  36. if (!isUnreliableValue && value === newValue) {
  37. const {
  38. selectionStart
  39. } = (0, _utils.getSelectionRange)(element);
  40. if (selectionStart === value.length) {
  41. // The value was changed as expected, but the cursor was moved to the end
  42. // TODO: this could probably be only applied when we work around a framework setter on the element in applyNative
  43. (0, _utils.setSelectionRange)(element, newSelectionStart, newSelectionStart);
  44. }
  45. }
  46. }
  47. const initial = Symbol('initial input value/textContent');
  48. const onBlur = Symbol('onBlur');
  49. /**
  50. * React tracks the changes on element properties.
  51. * This workaround tries to alter the DOM element without React noticing,
  52. * so that it later picks up the change.
  53. *
  54. * @see https://github.com/facebook/react/blob/148f8e497c7d37a3c7ab99f01dec2692427272b1/packages/react-dom/src/client/inputValueTracking.js#L51-L104
  55. */
  56. function applyNative(element, propName, propValue) {
  57. const descriptor = Object.getOwnPropertyDescriptor(element, propName);
  58. const nativeDescriptor = Object.getOwnPropertyDescriptor(element.constructor.prototype, propName);
  59. if (descriptor && nativeDescriptor) {
  60. Object.defineProperty(element, propName, nativeDescriptor);
  61. } // Keep track of the initial value to determine if a change event should be dispatched.
  62. // CONSTRAINT: We can not determine what happened between focus event and our first API call.
  63. if (element[initial] === undefined) {
  64. element[initial] = String(element[propName]);
  65. }
  66. element[propName] = propValue; // Add an event listener for the blur event to the capture phase on the window.
  67. // CONSTRAINT: Currently there is no cross-platform solution to unshift the event handler stack.
  68. // Our change event might occur after other event handlers on the blur event have been processed.
  69. if (!element[onBlur]) {
  70. var _element$ownerDocumen;
  71. (_element$ownerDocumen = element.ownerDocument.defaultView) == null ? void 0 : _element$ownerDocumen.addEventListener('blur', element[onBlur] = () => {
  72. const initV = element[initial]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  73. delete element[onBlur]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  74. delete element[initial];
  75. if (String(element[propName]) !== initV) {
  76. _dom.fireEvent.change(element);
  77. }
  78. }, {
  79. capture: true,
  80. once: true
  81. });
  82. }
  83. if (descriptor) {
  84. Object.defineProperty(element, propName, descriptor);
  85. }
  86. }