/*

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

*/
/**
@class Siesta.Test.ExtJS.Component

This is a mixin, with helper methods for testing functionality relating to Ext.Component. This mixin is being consumed by {@link Siesta.Test.ExtJS}.

*/
Role('Siesta.Test.ExtJS.Component', {

    requires: ['waitFor'],

    methods: {

        componentIsHidden : function (comp) {
            var el      = this.compToEl(comp);

            if (!el) return true

            return (comp.isHidden && comp.isHidden() || comp.isVisible && !comp.isVisible()) || !this.isElementVisible(el);
        },


        /**
         * Waits until the main element of the passed component is the 'top' element in the DOM. The callback will receive the passed component instance.
         *
         * @param {Ext.Component/String} component An Ext.Component instance or a ComponentQuery string. In the latter case,
         * this method will also wait until the component query find some component (meaning the component does not have to
         * be already created when waiting starts)
         * @param {Function} callback The callback to call after the component becomes visible
         * @param {Object} scope The scope for the callback
         * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
         */
        waitForComponentVisible: function (component, callback, scope, timeout) {
            var R       = Siesta.Resource('Siesta.Test.ExtJS.Component');
            var me      = this;

            if (this.typeOf(component) != 'String' && !this.isExtJSComponentQueryTarget(component)) {
                throw R.get('badInputText') + ': ' + component;
            }

            return this.waitFor({
                method          : function () {
                    var comp    = me.normalizeComponent(component, true)

                    if (!comp) return false

                    var el      = me.compToEl(comp);

                    return el && me.elementIsTop(el, true) && comp;
                },
                callback        : callback,
                scope           : scope,
                timeout         : timeout,
                assertionName   : 'waitForComponentVisible',
                description     : ' ' + R.get('component') + ' "' + (me.typeOf(component) == 'String' ? component : component.id) + '" ' + R.get('toBeVisible')
            });
        },


        /**
         * Waits until the main element of the passed component is not visible. The callback will receive the passed component instance.
         *
         * @param {Ext.Component/String} component An Ext.Component instance or a ComponentQuery string. In the latter case,
         * this method will also wait until the component query find some component (meaning the component does not have to
         * be already created when waiting starts)
         * @param {Function} callback The callback to call after the component becomes not visible
         * @param {Object} scope The scope for the callback
         * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
         */
        waitForComponentNotVisible: function (component, callback, scope, timeout) {
            var R       = Siesta.Resource('Siesta.Test.ExtJS.Component');
            var me      = this;

            if (this.typeOf(component) != 'String' && !this.isExtJSComponentQueryTarget(component)) {
                throw R.get('badInputText') + ': ' + component;
            }

            return this.waitFor({
                method          : function () {
                    var comp    = me.normalizeComponent(component, true)

                    if (!comp) return false

                    return me.componentIsHidden(comp) && comp
                },
                callback        : callback,
                scope           : scope,
                timeout         : timeout,
                assertionName   : 'waitForComponentNotVisible',
                description     : ' ' + R.get('component') + ' "' + (me.typeOf(component) == 'String' ? component : component.id) + '" ' + R.get('toNotBeVisible')
            });
        },


        /**
        * Waits until Ext.ComponentQuery detects some results from the passed query parameter. The callback will receive the result of the query.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} selector The component query phrase
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback to call after the xtype has been found
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForComponentQuery: function (selector, root, callback, scope, timeout) {
            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout = scope
                scope = callback
                callback = root
                root = this.getExt() && this.getExt().ComponentQuery
            }

            return this.waitFor({
                method      : function () {
                    var result = (root && root.query(selector) || this.getExt() && this.getExt().ComponentQuery.query(selector));
                    return result && result.length > 0 ? result : false;
                },
                callback    : callback,
                scope       : scope,
                timeout     : timeout,
                assertionName   : 'waitForComponentQuery',
                description     : ' ' + Siesta.Resource('Siesta.Test.ExtJS.Component', 'componentQuery') + ' "' + selector + '"'
            });
        },

        /**
        * Waits until {@link Siesta.Test.ExtJSCore#compositeQuery} detects some results from the passed query parameter. The callback will receive the result of the query.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} query The composite query phrase
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForCompositeQuery: function (query, root, callback, scope, timeout) {
            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout     = scope
                scope       = callback
                callback    = root
                root        = this.getExt().ComponentQuery
            }

            var me = this;

            return me.waitFor({
                method      : function () {
                    var result = me.compositeQuery(query, root, true);

                    return result.length > 0 ? result : false;
                },
                callback    : callback,
                scope       : scope,
                timeout     : timeout,

                assertionName   : 'waitForCompositeQuery',
                description     : ' ' + Siesta.Resource('Siesta.Test.ExtJS.Component', 'compositeQuery') + ' "' + query + '"'
            });
        },


        /**
        * Waits until {@link Siesta.Test.ExtJSCore#compositeQuery} does not detects any results from the passed query parameter.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} query The composite query phrase
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForCompositeQueryNotFound: function (query, root, callback, scope, timeout) {
            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout     = scope
                scope       = callback
                callback    = root
                root        = this.getExt().ComponentQuery
            }

            var me = this;

            return me.waitFor({
                method      : function () {
                    var result = me.compositeQuery(query, root, true);

                    return result.length > 0 ? false : true;
                },
                callback    : callback,
                scope       : scope,
                timeout     : timeout,

                assertionName   : 'waitForCompositeQueryNotFound',
                description     : ' ' + Siesta.Resource('Siesta.Test.ExtJS.Component', 'compositeQuery') + ' "' + query + '" ' + Siesta.Resource('Siesta.Test.ExtJS.Component', 'toReturnEmptyArray')
            });
        },



        /**
        * Shorthand alias for {@link #waitForComponentQuery}
        *
        * @param {String} query The component query phrase
        * @param {Ext.Container} root The container to start a component query from
        * @param {Function} callback The callback to call after the xtype has been found
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForCQ: function () {
            return this.waitForComponentQuery.apply(this, arguments);
        },


        /**
        * Alias for {@link #waitForComponentQueryNotFound}
        *
        * @param {String} query
        * @param {Function} callback
        * @param {Object} scope
        * @param {Number} timeout
        */
        waitForCQNotFound: function () {
            return this.waitForComponentQueryNotFound.apply(this, arguments);
        },


        /**
        * Waits until Ext.ComponentQuery from the passed query parameter is no longer found, and then calls the callback supplied.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} query The component query selector
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback to call after the xtype has been found
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForComponentQueryNotFound: function (query, root, callback, scope, timeout) {
            var R = Siesta.Resource('Siesta.Test.ExtJS.Component');

            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout     = scope
                scope       = callback
                callback    = root
                root        = this.getExt().ComponentQuery
            }

            return this.waitFor({
                method      : function () {
                    var result = root.query(query);
                    return result.length === 0 && result;
                },
                callback    : callback,
                scope       : scope,
                timeout     : timeout,
                assertionName   : 'waitForComponentQueryNotFound',
                description     : R.get('componentQuery') + ': ' + query + ' ' + R.get('toReturnEmpty')
            });
        },


        /**
        * Alias for {@link #waitForComponentQueryVisible}
        *
        * @param {String} query
        * @param {Function} callback
        * @param {Object} scope
        * @param {Number} timeout
        */
        waitForCQVisible: function () {
            return this.waitForComponentQueryVisible.apply(this, arguments);
        },

        /**
         * Alias for {@link #waitForComponentQueryNotVisible}
         *
         * @param {String} query
         * @param {Function} callback
         * @param {Object} scope
         * @param {Number} timeout
         */
        waitForCQNotVisible: function () {
            return this.waitForComponentQueryNotVisible.apply(this, arguments);
        },

        /**
        * Waits until all results of the `Ext.ComponentQuery` are detected and visible.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} query The component query selector
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback to call after the waiting has been completed
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForComponentQueryVisible: function (query, root, callback, scope, timeout) {
            var me  = this,
                R   = Siesta.Resource('Siesta.Test.ExtJS.Component'),
                Ext = me.getExt();

            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout     = scope
                scope       = callback
                callback    = root
                root        = Ext.ComponentQuery
            }

            var firstNonVisibleId
            var resultsLen

            return this.waitFor({
                method      : function () {
                    firstNonVisibleId   = null

                    var result          = root.query(query),
                        allVisible      = true

                    resultsLen          = result.length

                    if (resultsLen > 0) {

                        Joose.A.each(result, function (c) {
                            if (!c.rendered || !me.isElementVisible(c)) {
                                allVisible          = false

                                firstNonVisibleId   = c.id

                                return false
                            }
                        })

                        return allVisible && result
                    } else {
                        return false
                    }
                },
                callback        : callback,
                scope           : scope,
                timeout         : timeout,
                assertionName   : 'waitForComponentQueryVisible',
                description     : ' ' + R.get('componentQuery') + ': ' + query + ' to return a non-empty set of visible components',

                annotation      : function () {
                    // empty resultset
                    if (resultsLen === 0) return "No matching components"

                    // success - return nothing to not pollute the output with extra details
                    if (resultsLen > 0 && firstNonVisibleId == null) return ""

                    // non-empty resultset with some components hidden
                    if (resultsLen > 0 && firstNonVisibleId != null) return "The matching component [id=" + firstNonVisibleId + "] is not visible"
                }
            })
        },

        /**
         * Waits until the result of the `Ext.ComponentQuery` is either empty, or the found component(s) is hidden.
         *
         * The "root" argument of this method can be omitted.
         *
         * @param {String} query The component query selector
         * @param {Ext.Container} root The container to start a component query from. Optional
         * @param {Function} callback The callback to call after the xtype has been found
         * @param {Object} scope The scope for the callback
         * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
         */
        waitForComponentQueryNotVisible: function (query, root, callback, scope, timeout) {
            var me  = this,
                R   = Siesta.Resource('Siesta.Test.ExtJS.Component'),
                Ext = me.getExt();

            // no `root` supplied
            if (this.typeOf(root) == 'Function') {
                timeout     = scope
                scope       = callback
                callback    = root
                root        = Ext.ComponentQuery
            }

            var firstVisibleId
            var resultsLen

            return this.waitFor({
                method          : function () {
                    firstVisibleId  = null

                    var result = root.query(query),
                        allHidden = true;

                    resultsLen = result.length

                    if (resultsLen > 0) {
                        Joose.A.each(result, function (comp) {
                            if (!me.componentIsHidden(comp)) {
                                firstVisibleId  = comp.id

                                allHidden       = false;
                                return false;
                            }
                        });
                        return allHidden && result;
                    } else {
                        return true;
                    }
                },
                callback        : callback,
                scope           : scope,
                timeout         : timeout,
                assertionName   : 'waitForComponentQueryVisible',
                description     : ' ' + R.get('componentQuery') + ': ' + query + ' ' + R.get('toReturnHiddenCmp'),

                annotation      : function () {
                    // success - return nothing to not pollute the output with extra details
                    if (resultsLen === 0 || firstVisibleId == null) return ""

                    // non-empty resultset with some components visible
                    if (firstVisibleId != null) return "The matching component [id=" + firstVisibleId + "] is visible"
                }
            });
        },

        /**
        * Waits until the a component with the specified xtype can be detected by a simple ComponentQuery.
        *
        * The "root" argument of this method can be omitted.
        *
        * @param {String} xtype The component xtype to look for.
        * @param {Ext.Container} root The container to start a component query from. Optional
        * @param {Function} callback The callback to call after the xtype has been found
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForXType: function (xtype, root, callback, scope, timeout) {
            return this.waitForComponentQuery(xtype, root, callback, scope, timeout);
        },

        /**
        * Waits until the a component with the specified xtype can be detected by a simple ComponentQuery.
        *
        * @param {String} component The class name to wait for.
        * @param {Boolean} rendered true to also wait for the component to be rendered
        * @param {Function} callback The callback to call after the component has been found
        * @param {Object} scope The scope for the callback
        * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
        */
        waitForComponent: function (component, rendered, callback, scope, timeout) {
            var Ext         = this.getExt();
            var xtype

            if (Ext.isString(component)) {
                xtype = Ext.ClassManager.get(component).xtype;
            } else {
                xtype = component.xtype;
            }

            if (rendered) {
                xtype = xtype + '[rendered]';
            }

            return this.waitForXType(xtype, callback, scope, timeout);
        },

        /**
        * This assertion passes when the passed width and height matches the result of component.getSize()
        *
        * @param {Ext.Component/String} component An Ext.Component instance or a ComponentQuery
        * @param {Int} width
        * @param {Int} height
        * @param {String} [description] The description of the assertion
        */
        hasSize: function (component, width, height, description) {
            component = this.normalizeComponent(component);
            this.isDeeply(component.getSize(), { width: width, height: height }, description);
        },

        /**
        * This assertion passes when the passed x and y matches the result of component.getPosition()
        *
        * @param {Ext.Component/String} component An Ext.Component instance or a ComponentQuery
        * @param {Int} x
        * @param {Int} y
        * @param {String} [description] The description of the assertion
        */
        hasPosition: function (component, x, y, description) {
            component = this.normalizeComponent(component);
            this.isDeeply(component.getPosition(), [x, y], description);
        },


        /**
         * This assertion accepts variable number of Ext.Component instances (can be also provided as component query string).
         * Then it calls their "destroy" method and verifies that:
         * - there were no exceptions during destroy
         * - that each component was actually destoyed (since destroy can be canceled in the "beforedestroy" event listener)
         *
         * @param {Ext.Component/Array[Ext.Component]/String} components A single instance of Ext.Component, an array of such or a string with component query
         * @param {String} [description] The description of the assertion
         */
        destroysOk : function (components, description) {
            var Ext     = this.Ext();
            var R       = Siesta.Resource('Siesta.Test.ExtJS.Component');

            if (this.typeOf(components) != 'Array') {
                if (this.typeOf(components) == 'String')
                    components  = this.Ext().ComponentQuery.query(components);
                else
                    components  = [ components ]
            }

            if (!components.length) {
                this.fail(description, {
                    assertionName       : 'destroysOk',
                    annotation          : R.get('invalidDestroysOkInput')
                })

                return
            }

            var currentComp

            var e       = this.getExceptionCatcher()(function () {
                Joose.A.each(components, function (component) {
                    currentComp = component

                    component.destroy()
                })
            })

            if (e !== undefined) {
                this.fail(description, {
                    assertionName       : 'destroysOk',
                    got                 : e,
                    gotDesc             : R.get('exception'),
                    annotation          : R.get('exceptionAnnotation') + ' ' + currentComp.id
                })

                return
            }

            var me      = this

            var allDestroyed = Joose.A.each(components, function (component) {
                //          ExtJS                             ST
                if (!(component.isDestroyed || component.destroy == Ext.emptyFn)) {
                    me.fail(description, {
                        assertionName       : 'destroysOk',
                        annotation          : R.get('Component') + ' [' + component.id + '] ' + R.get('destroyFailed')
                    })

                    return false
                }
            })

            if (allDestroyed === false) return

            this.pass(description, {
                descTpl     : R.get('destroyPassed')
            })
        }
    }
});