decorator.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. 'use strict';
  2. var forEach = require('core-js/library/fn/array/for-each');
  3. var filter = require('core-js/library/fn/array/filter');
  4. var map = require('core-js/library/fn/array/map');
  5. var signature = require('call-signature');
  6. var decorate = require('./decorate');
  7. var keys = require('core-js/library/fn/object/keys');
  8. function Decorator (receiver, config) {
  9. this.receiver = receiver;
  10. this.config = config;
  11. this.onError = config.onError;
  12. this.onSuccess = config.onSuccess;
  13. this.signatures = map(config.patterns, parse);
  14. this.wrapOnlySignatures = map(config.wrapOnlyPatterns, parse);
  15. }
  16. Decorator.prototype.enhancement = function () {
  17. var that = this;
  18. var container = this.container();
  19. var wrappedMethods = [];
  20. function attach(matcherSpec, enhanced) {
  21. var matcher = matcherSpec.parsed;
  22. var methodName = detectMethodName(matcher.callee);
  23. if (typeof that.receiver[methodName] !== 'function' || wrappedMethods.indexOf(methodName) !== -1) {
  24. return;
  25. }
  26. var callSpec = {
  27. thisObj: that.receiver,
  28. func: that.receiver[methodName],
  29. numArgsToCapture: numberOfArgumentsToCapture(matcherSpec),
  30. matcherSpec: matcherSpec,
  31. enhanced: enhanced
  32. };
  33. container[methodName] = callSpec.enhancedFunc = decorate(callSpec, that);
  34. wrappedMethods.push(methodName);
  35. }
  36. forEach(filter(this.signatures, methodCall), function (matcher) {
  37. attach(matcher, true);
  38. });
  39. forEach(filter(this.wrapOnlySignatures, methodCall), function (matcher) {
  40. attach(matcher, false);
  41. });
  42. return container;
  43. };
  44. Decorator.prototype.container = function () {
  45. var basement = {};
  46. if (typeof this.receiver === 'function') {
  47. var candidates = filter(this.signatures, functionCall);
  48. var enhanced = true;
  49. if (candidates.length === 0) {
  50. enhanced = false;
  51. candidates = filter(this.wrapOnlySignatures, functionCall);
  52. }
  53. if (candidates.length === 1) {
  54. var callSpec = {
  55. thisObj: null,
  56. func: this.receiver,
  57. numArgsToCapture: numberOfArgumentsToCapture(candidates[0]),
  58. matcherSpec: candidates[0],
  59. enhanced: enhanced
  60. };
  61. basement = callSpec.enhancedFunc = decorate(callSpec, this);
  62. }
  63. }
  64. return basement;
  65. };
  66. Decorator.prototype.concreteAssert = function (callSpec, invocation, context) {
  67. var func = callSpec.func;
  68. var thisObj = this.config.bindReceiver ? callSpec.thisObj : invocation.thisObj;
  69. var enhanced = callSpec.enhanced;
  70. var args = invocation.values;
  71. var message = invocation.message;
  72. var matcherSpec = callSpec.matcherSpec;
  73. if (context && typeof this.config.modifyMessageBeforeAssert === 'function') {
  74. message = this.config.modifyMessageBeforeAssert({originalMessage: message, powerAssertContext: context});
  75. }
  76. args = args.concat(message);
  77. var data = {
  78. thisObj: invocation.thisObj,
  79. assertionFunction: callSpec.enhancedFunc,
  80. originalMessage: message,
  81. defaultMessage: matcherSpec.defaultMessage,
  82. matcherSpec: matcherSpec,
  83. enhanced: enhanced,
  84. args: args
  85. };
  86. if (context) {
  87. data.powerAssertContext = context;
  88. }
  89. return this._callFunc(func, thisObj, args, data);
  90. };
  91. // see: https://github.com/twada/empower-core/pull/8#issuecomment-173480982
  92. Decorator.prototype._callFunc = function (func, thisObj, args, data) {
  93. var ret;
  94. try {
  95. ret = func.apply(thisObj, args);
  96. } catch (e) {
  97. data.assertionThrew = true;
  98. data.error = e;
  99. return this.onError.call(thisObj, data);
  100. }
  101. data.assertionThrew = false;
  102. data.returnValue = ret;
  103. return this.onSuccess.call(thisObj, data);
  104. };
  105. function numberOfArgumentsToCapture (matcherSpec) {
  106. var matcher = matcherSpec.parsed;
  107. var len = matcher.args.length;
  108. var lastArg;
  109. if (0 < len) {
  110. lastArg = matcher.args[len - 1];
  111. if (lastArg.name === 'message' && lastArg.optional) {
  112. len -= 1;
  113. }
  114. }
  115. return len;
  116. }
  117. function detectMethodName (callee) {
  118. if (callee.type === 'MemberExpression') {
  119. return callee.member;
  120. }
  121. return null;
  122. }
  123. function functionCall (matcherSpec) {
  124. return matcherSpec.parsed.callee.type === 'Identifier';
  125. }
  126. function methodCall (matcherSpec) {
  127. return matcherSpec.parsed.callee.type === 'MemberExpression';
  128. }
  129. function parse(matcherSpec) {
  130. if (typeof matcherSpec === 'string') {
  131. matcherSpec = {pattern: matcherSpec};
  132. }
  133. var ret = {};
  134. forEach(keys(matcherSpec), function (key) {
  135. ret[key] = matcherSpec[key];
  136. });
  137. ret.parsed = signature.parse(matcherSpec.pattern);
  138. return ret;
  139. }
  140. module.exports = Decorator;