"use strict"; const arrayProto = require("@sinonjs/commons").prototypes.array; const mockExpectation = require("./mock-expectation"); const proxyCallToString = require("./proxy-call").toString; const extend = require("./util/core/extend"); const deepEqual = require("@sinonjs/samsam").deepEqual; const wrapMethod = require("./util/core/wrap-method"); const usePromiseLibrary = require("./util/core/use-promise-library"); const concat = arrayProto.concat; const filter = arrayProto.filter; const forEach = arrayProto.forEach; const every = arrayProto.every; const join = arrayProto.join; const push = arrayProto.push; const slice = arrayProto.slice; const unshift = arrayProto.unshift; function mock(object) { if (!object || typeof object === "string") { return mockExpectation.create(object ? object : "Anonymous mock"); } return mock.create(object); } function each(collection, callback) { const col = collection || []; forEach(col, callback); } function arrayEquals(arr1, arr2, compareLength) { if (compareLength && arr1.length !== arr2.length) { return false; } return every(arr1, function (element, i) { return deepEqual(arr2[i], element); }); } extend(mock, { create: function create(object) { if (!object) { throw new TypeError("object is null"); } const mockObject = extend.nonEnum({}, mock, { object: object }); delete mockObject.create; return mockObject; }, expects: function expects(method) { if (!method) { throw new TypeError("method is falsy"); } if (!this.expectations) { this.expectations = {}; this.proxies = []; this.failures = []; } if (!this.expectations[method]) { this.expectations[method] = []; const mockObject = this; wrapMethod(this.object, method, function () { return mockObject.invokeMethod(method, this, arguments); }); push(this.proxies, method); } const expectation = mockExpectation.create(method); expectation.wrappedMethod = this.object[method].wrappedMethod; push(this.expectations[method], expectation); usePromiseLibrary(this.promiseLibrary, expectation); return expectation; }, restore: function restore() { const object = this.object; each(this.proxies, function (proxy) { if (typeof object[proxy].restore === "function") { object[proxy].restore(); } }); }, verify: function verify() { const expectations = this.expectations || {}; const messages = this.failures ? slice(this.failures) : []; const met = []; each(this.proxies, function (proxy) { each(expectations[proxy], function (expectation) { if (!expectation.met()) { push(messages, String(expectation)); } else { push(met, String(expectation)); } }); }); this.restore(); if (messages.length > 0) { mockExpectation.fail(join(concat(messages, met), "\n")); } else if (met.length > 0) { mockExpectation.pass(join(concat(messages, met), "\n")); } return true; }, usingPromise: function usingPromise(promiseLibrary) { this.promiseLibrary = promiseLibrary; return this; }, invokeMethod: function invokeMethod(method, thisValue, args) { /* if we cannot find any matching files we will explicitly call mockExpection#fail with error messages */ /* eslint consistent-return: "off" */ const expectations = this.expectations && this.expectations[method] ? this.expectations[method] : []; const currentArgs = args || []; let available; const expectationsWithMatchingArgs = filter( expectations, function (expectation) { const expectedArgs = expectation.expectedArguments || []; return arrayEquals( expectedArgs, currentArgs, expectation.expectsExactArgCount, ); }, ); const expectationsToApply = filter( expectationsWithMatchingArgs, function (expectation) { return ( !expectation.met() && expectation.allowsCall(thisValue, args) ); }, ); if (expectationsToApply.length > 0) { return expectationsToApply[0].apply(thisValue, args); } const messages = []; let exhausted = 0; forEach(expectationsWithMatchingArgs, function (expectation) { if (expectation.allowsCall(thisValue, args)) { available = available || expectation; } else { exhausted += 1; } }); if (available && exhausted === 0) { return available.apply(thisValue, args); } forEach(expectations, function (expectation) { push(messages, ` ${String(expectation)}`); }); unshift( messages, `Unexpected call: ${proxyCallToString.call({ proxy: method, args: args, })}`, ); const err = new Error(); if (!err.stack) { // PhantomJS does not serialize the stack trace until the error has been thrown try { throw err; } catch (e) { /* empty */ } } push( this.failures, `Unexpected call: ${proxyCallToString.call({ proxy: method, args: args, stack: err.stack, })}`, ); mockExpectation.fail(join(messages, "\n")); }, }); module.exports = mock;