    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 Bryntum AB
    https://bryntum.com/contact
    https://bryntum.com/products/siesta/license

    */
    /**
    @class Siesta.Test.Function

    This is a mixin, with helper methods for testing functionality relating to Functions (such as spies). This mixin is consumed by {@link Siesta.Test}

    */
    Role('Siesta.Test.Function', {

        methods : {
             /**
             * This assertion passes if the function is called at least one time during the test life span.
             *
             * @param {Function/String} fn The function itself or the name of the function on the host object (2nd argument)
             * @param {Object} host The "owner" of the method
             * @param {String} [desc] The description of the assertion.
             */
            isCalled : function(fn, obj, desc) {
                this.isCalledNTimes(fn, obj, 1, desc, true);
            },

            /**
             * This assertion passes if the function is called exactly one time during the test life span.
             *
             * @param {Function/String} fn The function itself or the name of the function on the host object (2nd argument)
             * @param {Object} host The "owner" of the method
             * @param {String} [desc] The description of the assertion.
             */
            isCalledOnce : function(fn, obj, desc) {
                this.isCalledNTimes(fn, obj, 1, desc, false);
            },

            /**
             * This assertion passes if the function is called exactly (n) times during the test life span.
             *
             * @param {Function/String} fn The function itself or the name of the function on the host object (2nd argument)
             * @param {Object} host The "owner" of the method
             * @param {Number} n The expected number of calls
             * @param {String} [desc] The description of the assertion.
             */
            isCalledNTimes : function(fn, obj, n, desc, isGreaterEqual) {
                var me          = this,
                    prop        = typeof fn === "string" ? fn : me.getPropertyName(obj, fn);

                var counter     = 0;
                var original    = obj[ prop ]

                obj[ prop ]     = function () { counter++; return original.apply(this, arguments) }

                desc            = desc ? (desc + ' ') : '';

                this.on('beforetestfinalizeearly', function () {
                    var R       = Siesta.Resource('Siesta.Test.Function');

                    if (counter === n || (isGreaterEqual && counter > n)) {
                        me.pass(desc || (prop + ' ' + R.get('methodCalledExactly').replace('{n}', n)));
                    } else {

                        me.fail(desc || prop, {
                            assertionName       : 'isCalledNTimes ' + prop,
                            got                 : counter,
                            need                : n,
                            needDesc            : R.get("Need") + " " + (isGreaterEqual ? R.get('atLeast') : R.get('exactly')) + " "
                        });
                    }

                    obj[ prop ]     = original
                });
            },

            /**
             * This assertion passes if the function is not called during the test life span.
             *
             * @param {Function/String} fn The function itself or the name of the function on the host object (2nd argument)
             * @param {Object} host The "owner" of the method
             * @param {String} [desc] The description of the assertion.
             */
            isntCalled : function(fn, obj, desc) {
                this.isCalledNTimes(fn, obj, 0, desc);
            },

            getPropertyName : function(host, obj) {
                for (var o in host) {
                    if (host[o] === obj) return o;
                }
            },

            /**
             * This assertion passes when the supplied class method is called exactly (n) times during the test life span.
             * Under "class method" here we mean the function in the prototype. Note, that this assertion counts calls to the method in *any* class instance.
             *
             * The `className` parameter can be supplied as a class constructor function or as a string, representing the class
             * name. In the latter case the `class` will be eval'ed to get a reference to the class constructor.
             *
             * For example:

        StartTest(function (t) {

            function machine(type, version) {
                this.machineInfo = {
                    type        : type,
                    version     : version
                };
            };

            machine.prototype.update = function (type, version) {
                this.setVersion(type);
                this.setType(version);
            };

            machine.prototype.setVersion = function (data) {
                this.machineInfo.version = data;
            };

            machine.prototype.setType = function (data) {
                this.machineInfo.type = data;
            };

            t.methodIsCalled("setVersion", machine, "Checking if method 'setVersion' has been called");
            t.methodIsCalled("setType", machine, "Checking if method 'setType' has been called");

            var m = new machine('rover', '0.1.2');

            m.update('3.2.1', 'New Rover');
        });

             *
             * This assertion is useful when testing for example an Ext JS class where event listeners are added during
             * class instantiation time, which means you need to observe the prototype method before instantiation.
             *
             * @param {Function/String} fn The function itself or the name of the method on the class (2nd argument)
             * @param {Function/String} className The constructor function or the name of the class that contains the method
             * @param {Number} n The expected number of calls
             * @param {String} [desc] The description of the assertion
             */
            methodIsCalledNTimes: function(fn, className, n, desc, isGreaterEqual){
                var me          = this,
                    counter     = 0;
                var R           = Siesta.Resource('Siesta.Test.Function');

                desc            = desc ? (desc + ' ') : '';

                try {
                    if (me.typeOf(className) == 'String') className = me.global.eval(className)
                } catch (e) {
                    me.fail(desc, {
                        assertionName       : 'isMethodCalled',
                        annotation          : R.get('exceptionEvalutingClass').replace('{e}', e) + "[" + className + "]"
                    })

                    return
                }

                var prototype   = className.prototype;
                var prop        = typeof fn === "string" ? fn : me.getPropertyName(prototype, fn);

                me.on('beforetestfinalizeearly', function () {
                    if (counter === n || (isGreaterEqual && counter > n)) {
                        me.pass(desc || (prop + ' ' + R.get('methodCalledExactly').replace('{n}', n)));
                    } else {
                        me.fail(desc || prop, {
                            assertionName       : 'methodIsCalledNTimes ' + prop,
                            got                 : counter,
                            need                : n ,
                            needDesc            : R.get("Need") + " " + (isGreaterEqual ? R.get('atLeast') : R.get('exactly')) + " "
                        });
                    }
                });

                fn                  = prototype[ prop ];
                prototype[ prop ]   = function () { counter++; return fn.apply(this, arguments); };
            },

            /**
             * This assertion passes if the class method is called at least one time during the test life span.
             *
             * See {@link #methodIsCalledNTimes} for more details.
             *
             * @param {Function/String} fn The function itself or the name of the method on the class (2nd argument)
             * @param {Function/String} className The class constructor function or name of the class that contains the method
             * @param {String} [desc] The description of the assertion.
             */
            methodIsCalled : function(fn, className, desc) {
                this.methodIsCalledNTimes(fn, className, 1, desc, true);
            },

            /**
             * This assertion passes if the class method is not called during the test life span.
             *
             * See {@link #methodIsCalledNTimes} for more details.
             *
             * @param {Function/String} fn The function itself or the name of the method on the class (2nd argument)
             * @param {Function/String} className The class constructor function or name of the class that contains the method
             * @param {String} [desc] The description of the assertion.
             */
            methodIsntCalled : function(fn, className, desc) {
                this.methodIsCalledNTimes(fn, className, 0, desc);
            }
        }
    });