chai-spies-plugin.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /* eslint-disable jsdoc/require-jsdoc */
  2. /**
  3. * Chai spies plugin
  4. *
  5. * @param {*} chai
  6. * @param {*} _
  7. */
  8. /* prettier-ignore */
  9. export function chaiSpiesPlugin(chai, _) {
  10. const Assertion = chai.Assertion;
  11. Assertion.addProperty('spy', function () {
  12. this.assert(
  13. this._obj.__isSpy === true
  14. , 'expected ' + this._obj + ' to be a spy'
  15. , 'expected ' + this._obj + ' to not be a spy');
  16. return this;
  17. });
  18. function assertCalled (n) {
  19. new Assertion(this._obj).to.be.spy;
  20. const spy = this._obj;
  21. if (n != undefined) {
  22. this.assert(
  23. spy.calls.length === n
  24. , 'expected ' + this._obj + ' to have been called #{exp} but got #{act}'
  25. , 'expected ' + this._obj + ' to have not been called #{exp}'
  26. , n
  27. , spy.calls.length
  28. );
  29. } else {
  30. this.assert(
  31. spy.called === true
  32. , 'expected ' + this._obj + ' to have been called'
  33. , 'expected ' + this._obj + ' to not have been called'
  34. );
  35. }
  36. }
  37. function assertCalledChain () {
  38. new Assertion(this._obj).to.be.spy;
  39. }
  40. Assertion.addChainableMethod('called', assertCalled, assertCalledChain);
  41. Assertion.addProperty('once', function () {
  42. new Assertion(this._obj).to.be.spy;
  43. this.assert(
  44. this._obj.calls.length === 1
  45. , 'expected ' + this._obj + ' to have been called once but got #{act}'
  46. , 'expected ' + this._obj + ' to not have been called once'
  47. , 1
  48. , this._obj.calls.length );
  49. });
  50. Assertion.addProperty('twice', function () {
  51. new Assertion(this._obj).to.be.spy;
  52. this.assert(
  53. this._obj.calls.length === 2
  54. , 'expected ' + this._obj + ' to have been called twice but got #{act}'
  55. , 'expected ' + this._obj + ' to not have been called twice'
  56. , 2
  57. , this._obj.calls.length
  58. );
  59. });
  60. function nthCallWith(spy, n, expArgs) {
  61. if (spy.calls.length <= n) return false;
  62. const actArgs = spy.calls[n].args;
  63. if (actArgs.length !== expArgs.length) return false;
  64. // проверка каждого ожидаемого аргумента на строгое
  65. // соответствие позиции и значения
  66. for (let i = 0; i < expArgs.length; i++) {
  67. if (!_.eql(actArgs[i], expArgs[i])) {
  68. return false;
  69. }
  70. }
  71. return true;
  72. }
  73. function numberOfCallsWith(spy, expArgs) {
  74. let found = 0;
  75. const calls = spy.calls;
  76. for (let i = 0; i < calls.length; i++) {
  77. if (nthCallWith(spy, i, expArgs)) {
  78. found++;
  79. }
  80. }
  81. return found;
  82. }
  83. Assertion.addProperty('first', function () {
  84. if (this._obj.__isSpy) {
  85. _.flag(this, 'spy nth call with', 1);
  86. }
  87. });
  88. Assertion.addProperty('second', function () {
  89. if (this._obj.__isSpy) {
  90. _.flag(this, 'spy nth call with', 2);
  91. }
  92. });
  93. Assertion.addProperty('third', function () {
  94. if (this._obj.__isSpy) {
  95. _.flag(this, 'spy nth call with', 3);
  96. }
  97. });
  98. Assertion.addProperty('on');
  99. Assertion.addChainableMethod('nth', function (n) {
  100. if (this._obj.__isSpy) {
  101. _.flag(this, 'spy nth call with', n);
  102. }
  103. });
  104. function generateOrdinalNumber(n) {
  105. if (n === 1) return 'first';
  106. if (n === 2) return 'second';
  107. if (n === 3) return 'third';
  108. return n + 'th';
  109. }
  110. function assertWith() {
  111. new Assertion(this._obj).to.be.spy;
  112. const expArgs = [].slice.call(arguments, 0)
  113. , spy = this._obj
  114. , calls = spy.calls
  115. , always = _.flag(this, 'spy always')
  116. , nthCall = _.flag(this, 'spy nth call with');
  117. if (always) {
  118. const passed = numberOfCallsWith(spy, expArgs);
  119. this.assert(
  120. arguments.length
  121. ? calls.length && passed === calls.length
  122. : calls.length === 0
  123. , 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
  124. , 'expected ' + this._obj + ' to have not always been called with #{exp}'
  125. , expArgs
  126. );
  127. } else if (nthCall) {
  128. const ordinalNumber = generateOrdinalNumber(nthCall),
  129. actArgs = calls[nthCall - 1];
  130. new Assertion(this._obj).to.be.have.been.called.min(nthCall);
  131. this.assert(
  132. nthCallWith(spy, nthCall - 1, expArgs)
  133. , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}'
  134. , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with #{exp}'
  135. , expArgs
  136. , actArgs
  137. );
  138. } else {
  139. const passed = numberOfCallsWith(spy, expArgs);
  140. this.assert(
  141. passed > 0
  142. , 'expected ' + this._obj + ' to have been called with #{exp}'
  143. , 'expected ' + this._obj + ' to have not been called with #{exp} but got ' + passed + ' times'
  144. , expArgs
  145. );
  146. }
  147. }
  148. function assertWithChain () {
  149. if (this._obj.__isSpy) {
  150. _.flag(this, 'spy with', true);
  151. }
  152. }
  153. Assertion.addChainableMethod('with', assertWith, assertWithChain);
  154. Assertion.addProperty('always', function () {
  155. if (this._obj.__isSpy) {
  156. _.flag(this, 'spy always', true);
  157. }
  158. });
  159. Assertion.addMethod('exactly', function () {
  160. new Assertion(this._obj).to.be.spy;
  161. const args = [].slice.call(arguments, 0);
  162. this.assert(
  163. this._obj.calls.length === args[0]
  164. , 'expected ' + this._obj + ' to have been called #{exp} times but got #{act}'
  165. , 'expected ' + this._obj + ' to not have been called #{exp} times'
  166. , args[0]
  167. , this._obj.calls.length
  168. );
  169. });
  170. function above (_super) {
  171. return function (n) {
  172. if (this._obj.__isSpy) {
  173. new Assertion(this._obj).to.be.spy;
  174. this.assert(
  175. this._obj.calls.length > n
  176. , 'expected ' + this._obj + ' to have been called more than #{exp} times but got #{act}'
  177. , 'expected ' + this._obj + ' to have been called at most #{exp} times but got #{act}'
  178. , n
  179. , this._obj.calls.length
  180. );
  181. } else {
  182. _super.apply(this, arguments);
  183. }
  184. }
  185. }
  186. Assertion.overwriteMethod('above', above);
  187. Assertion.overwriteMethod('gt', above);
  188. function below (_super) {
  189. return function (n) {
  190. if (this._obj.__isSpy) {
  191. new Assertion(this._obj).to.be.spy;
  192. this.assert(
  193. this._obj.calls.length < n
  194. , 'expected ' + this._obj + ' to have been called fewer than #{exp} times but got #{act}'
  195. , 'expected ' + this._obj + ' to have been called at least #{exp} times but got #{act}'
  196. , n
  197. , this._obj.calls.length
  198. );
  199. } else {
  200. _super.apply(this, arguments);
  201. }
  202. }
  203. }
  204. Assertion.overwriteMethod('below', below);
  205. Assertion.overwriteMethod('lt', below);
  206. function min (_super) {
  207. return function (n) {
  208. if (this._obj.__isSpy) {
  209. new Assertion(this._obj).to.be.spy;
  210. this.assert(
  211. this._obj.calls.length >= n
  212. , 'expected ' + this._obj + ' to have been called at least #{exp} times but got #{act}'
  213. , 'expected ' + this._obj + ' to have been called fewer than #{exp} times but got #{act}'
  214. , n
  215. , this._obj.calls.length
  216. );
  217. } else {
  218. _super.apply(this, arguments);
  219. }
  220. }
  221. }
  222. Assertion.overwriteMethod('min', min);
  223. Assertion.overwriteMethod('least', min);
  224. function max (_super) {
  225. return function (n) {
  226. if (this._obj.__isSpy) {
  227. new Assertion(this._obj).to.be.spy;
  228. this.assert(
  229. this._obj.calls.length <= n
  230. , 'expected ' + this._obj + ' to have been called at most #{exp} times but got #{act}'
  231. , 'expected ' + this._obj + ' to have been called more than #{exp} times but got #{act}'
  232. , n
  233. , this._obj.calls.length
  234. );
  235. } else {
  236. _super.apply(this, arguments);
  237. }
  238. }
  239. }
  240. Assertion.overwriteMethod('max', max);
  241. Assertion.overwriteMethod('most', max);
  242. }