interceptor.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import { isElementType } from '../utils/misc/isElementType.js';
  2. import '../utils/dataTransfer/Clipboard.js';
  3. import { startTrackValue, trackOrSetValue } from './trackValue.js';
  4. import { setUISelectionClean, setUISelectionRaw, getUIValue, setUIValueClean, isUIValue, isUISelectionStart } from './UI.js';
  5. const Interceptor = Symbol('Interceptor for programmatical calls');
  6. function prepareInterceptor(element, propName, interceptorImpl) {
  7. const prototypeDescriptor = Object.getOwnPropertyDescriptor(element.constructor.prototype, propName);
  8. const objectDescriptor = Object.getOwnPropertyDescriptor(element, propName);
  9. const target = (prototypeDescriptor === null || prototypeDescriptor === undefined ? undefined : prototypeDescriptor.set) ? 'set' : 'value';
  10. /* istanbul ignore if */ if (typeof (prototypeDescriptor === null || prototypeDescriptor === undefined ? undefined : prototypeDescriptor[target]) !== 'function' || prototypeDescriptor[target][Interceptor]) {
  11. throw new Error(`Element ${element.tagName} does not implement "${String(propName)}".`);
  12. }
  13. function intercept(...args) {
  14. const { applyNative = false, realArgs, then } = interceptorImpl.call(this, ...args);
  15. const realFunc = (!applyNative && objectDescriptor || prototypeDescriptor)[target];
  16. if (target === 'set') {
  17. realFunc.call(this, realArgs);
  18. } else {
  19. realFunc.call(this, ...realArgs);
  20. }
  21. then === null || then === undefined ? undefined : then();
  22. }
  23. intercept[Interceptor] = Interceptor;
  24. Object.defineProperty(element, propName, {
  25. ...objectDescriptor !== null && objectDescriptor !== undefined ? objectDescriptor : prototypeDescriptor,
  26. [target]: intercept
  27. });
  28. }
  29. function prepareValueInterceptor(element) {
  30. prepareInterceptor(element, 'value', function interceptorImpl(v) {
  31. const isUI = isUIValue(v);
  32. if (isUI) {
  33. startTrackValue(this);
  34. }
  35. return {
  36. applyNative: !!isUI,
  37. realArgs: sanitizeValue(this, v),
  38. then: isUI ? undefined : ()=>trackOrSetValue(this, String(v))
  39. };
  40. });
  41. }
  42. function sanitizeValue(element, v) {
  43. // Workaround for JSDOM
  44. if (isElementType(element, 'input', {
  45. type: 'number'
  46. }) && String(v) !== '' && !Number.isNaN(Number(v))) {
  47. // Setting value to "1." results in `null` in JSDOM
  48. return String(Number(v));
  49. }
  50. return String(v);
  51. }
  52. function prepareSelectionInterceptor(element) {
  53. prepareInterceptor(element, 'setSelectionRange', function interceptorImpl(start, ...others) {
  54. const isUI = isUISelectionStart(start);
  55. return {
  56. applyNative: !!isUI,
  57. realArgs: [
  58. Number(start),
  59. ...others
  60. ],
  61. then: ()=>isUI ? undefined : setUISelectionClean(element)
  62. };
  63. });
  64. prepareInterceptor(element, 'selectionStart', function interceptorImpl(v) {
  65. return {
  66. realArgs: v,
  67. then: ()=>setUISelectionClean(element)
  68. };
  69. });
  70. prepareInterceptor(element, 'selectionEnd', function interceptorImpl(v) {
  71. return {
  72. realArgs: v,
  73. then: ()=>setUISelectionClean(element)
  74. };
  75. });
  76. prepareInterceptor(element, 'select', function interceptorImpl() {
  77. return {
  78. realArgs: [],
  79. then: ()=>setUISelectionRaw(element, {
  80. anchorOffset: 0,
  81. focusOffset: getUIValue(element).length
  82. })
  83. };
  84. });
  85. }
  86. function prepareRangeTextInterceptor(element) {
  87. prepareInterceptor(element, 'setRangeText', function interceptorImpl(...realArgs) {
  88. return {
  89. realArgs,
  90. then: ()=>{
  91. setUIValueClean(element);
  92. setUISelectionClean(element);
  93. }
  94. };
  95. });
  96. }
  97. export { prepareInterceptor, prepareRangeTextInterceptor, prepareSelectionInterceptor, prepareValueInterceptor };