interceptor.js 4.0 KB

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