Source: br-test/src/br/test/Utils.js

/**
 * @module br/test/Utils
 */

/* global br, require, presenter_knockout, jQuery */
require('keyboard-event');
var jQuery = require('jquery');
var ko = require('presenter-knockout');
var FileUtility = require('br/core/File');
var Utility = require('br/core/Utility');
var Errors = require('br/Errors');

/**
 * @class
 * @alias module:br/test/Utils
 *
 * @classdesc
 * Utility class containing static methods that can be useful for tests.
 */
var Utils = function() {
};

Utils.pLoadedAndAttachedCSSElements = [];

/**
* Fires a DOM Event in a cross Browser compatible way.
*
* @static
* @param {DOMElement} element The DOM Element the Event is fired from
* @param {String} eventString The Event to be fired without 'on', e.g. 'click', 'keydown'
* @param {String} [character] A character associated with typing events
*/
Utils.fireDomEvent = function(eElement, eventString, character) {
	var element = (eElement instanceof jQuery) ? eElement.context : eElement;
	var evt;

	if(document.createEvent) {
		evt = document.createEvent('HTMLEvents');
		if (typeof character !== 'undefined') {
			evt.which = Utils.getKeyCodeForChar(character);
		}
		evt.initEvent(eventString, true, true);
		if (element) {
			return !element.dispatchEvent(evt);
		}
		return false;
	}
	else if(document.createEventObject) {
		if(element.nodeName == 'OPTION') {
			do {
				element = element.parentNode;
			} while(element.nodeName != 'SELECT');
		}
		
		evt = document.createEventObject();
		if (character) {
			evt.keyCode = Utils.getKeyCodeForChar(character);
		}
		element.fireEvent('on' + eventString, evt);
	}
};

/**
* Returns the Keycode of a letter.
*
* @static
* @param {String} character a single Character to get the Keycode for
* @returns {Number} keyCode The key code for the specified char.
*/
Utils.getKeyCodeForChar = function(character) {
	if (character.toString().length !== 1){
		throw Errors.CustomError(
			Errors.INVALID_TEST,
			'getKeyCodeForChar Error! ' + character + ' should only be a single Character'
		);
	}

	return character.charCodeAt(0);
};

/**
* Fires a DOM KeyboardEvent in a cross Browser compatible way.
*
* @static
* @param {DOMElement} element The DOM Element the Event is fired from
* @param {String} eventString The Event to be fired without 'on', e.g. 'keydown'
* @param {String} key This parameter is deprecated. Pass in this value as <code>options.key</code>.
* @param {Map} options A map of values, passed in to the <code>KeyboardEvent</code> constructor, associated with typing events.
*/
Utils.fireKeyEvent = function(element, eventString, key, options) {
	options = options || {};
	options.key = options.key || key;
	options.bubbles = true;

	var evt = new KeyboardEvent(eventString, options);
	if (element.dispatchEvent) {
		element.dispatchEvent(evt);
	}
	else {
		element.fireEvent('on' + eventString, evt);
	}
};

/**
* Fires a DOM MouseEvents in a cross Browser compatible way.
*
* @static
* @param {DOMElement} element The DOM Element the Event is fired from
* @param {String} eventString The Event to be fired without 'on', e.g. 'click'
* @param {Map} options a map of values, passed in to <code>initMouseEvent</code>, associated with mouse events.
*/
Utils.fireMouseEvent = function(element, eventString, options) {
	var args = Utils.fireMouseEvent._mergeDefaultMouseEventArgumentsWithArgumentsMap(options || {}),
		evt;

	if (document.createEvent) {
		evt = Utils.fireMouseEvent._getMouseEventDOMStandard(eventString, args.canBubble, args.cancelable, args.view, args.detail, args.screenX, args.screenY,
			args.clientX, args.clientY, args.ctrlKey, args.altKey, args.shiftKey, args.metaKey, args.button, args.relatedTarget);
		element.dispatchEvent(evt);

	} else if (element.fireEvent) {

		if (Utils._isClickEventWithNoEventOptionsAndKOIsAvailable(eventString, element, options)) {
			/*
			* The reason for this KO call is due to jQuery not behaving like a browser and only setting a checked value
			* after calling the event handlers. This code should only run in IE8 (element.fireEvent check above).
			*
			* There is a comment in the KO code which explains why this is required.
			*/
			presenter_knockout.utils.triggerEvent(element, eventString);
		} else if (Utils._isClickEventWithNoEventOptions(eventString, element, options)) {
			jQuery(element).click();
		} else {
			evt = Utils.fireMouseEvent._getMouseEventIENonStandard(args.canBubble, args.cancelable, args.view, args.detail, args.screenX, args.screenY,
					args.clientX, args.clientY, args.ctrlKey, args.altKey, args.shiftKey, args.metaKey, args.button, args.relatedTarget);
			element.fireEvent('on' + eventString, evt);
		}
	}
};

/**
* Attaches CSS files to the test page. Cleaning them up is done via the <code>removeLoadedAndAttachedCSSFromPage</code>
* method.
*
* @static
* @param {Array} cssFiels list of css file URLs to be loaded into the test page.
*/
Utils.loadCSSAndAttachToPage = function(cssFiles) {
	var elHead = document.getElementsByTagName('head')[0],
		cssCode, cssEl;
	for (var i = 0, len = cssFiles.length; i < len; ++i) {
		cssCode = FileUtility.readFileSync(cssFiles[i]);
		cssEl = document.createElement('style');

		cssEl.type = 'text/css';

		try {
			cssEl.appendChild(document.createTextNode(cssCode));
		} catch (e) {
			//IE workaround
			if (typeof cssEl.styleSheet.cssText !== 'undefined') {
				cssEl.styleSheet.cssText += cssCode;
			} else {
				cssEl.styleSheet.cssText = cssCode;
			}
		}

		Utils.pLoadedAndAttachedCSSElements.push(elHead.appendChild(cssEl));
	}
};

/**
* Attaches CSS files to the test page. Cleaning them up is done via the <code>removeLoadedAndAttachedCSSFromPage</code>
* method.
*
* @static
* @param {Array} cssFiles list of css file URLs to be loaded into the test page.
*/
Utils.removeLoadedAndAttachedCSSFromPage = function() {
	var cssElements = Utils.pLoadedAndAttachedCSSElements,
		cssEl;

	while (cssElements.length) {
		cssEl = cssElements.shift();
		cssEl.parentNode.removeChild(cssEl);
	}
};

/**
* Fires a DOM scroll event in a cross Browser compatible way.
*
* @static
* @param {DOMElement} element The DOM Element the Event is fired from
*/
Utils.fireScrollEvent = function(element) {
	if (document.createEvent) {
		// FF
		var evt = document.createEvent('HTMLEvents');
		evt.initEvent('scroll', true, true);
		element.dispatchEvent(evt);
	} else if (document.createEventObject) {
		// IE
		element.fireEvent('onscroll');
	}
};

/** @private */
Utils.fireMouseEvent._mergeDefaultMouseEventArgumentsWithArgumentsMap = function(config) {
	return {
		canBubble : config.canBubble !== false ? true : false,
		cancelable : config.cancelable !== false ? true : false,
		view : config.view ? config.view : window,
		detail : config.detail ? config.detail : 0,
		screenX : config.screenX ? config.screenX : 0,
		screenY : config.screenY ? config.screenY : 0,
		clientX : config.clientX ? config.clientX : 0,
		clientY : config.clientY ? config.clientY : 0,
		ctrlKey : config.ctrlKey ? config.ctrlKey : false,
		altKey : config.altKey ? config.altKey : false,
		shiftKey : config.shiftKey ? config.shiftKey : false,
		metaKey : config.metaKey ? config.metaKey : false,
		button : config.button ? config.button : 0,
		relatedTarget : config.relatedTarget ? config.relatedTarget : null
	};
};

/** @private */
Utils.fireMouseEvent._getMouseEventDOMStandard = function(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget) {
	var evt = document.createEvent('MouseEvents');
	evt.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
	return evt;
};

/** @private */
Utils.fireMouseEvent._getMouseEventIENonStandard = function(canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget) {
	//create an IE event object
	var customEvent = document.createEventObject();

	//assign available properties
	customEvent.bubbles = canBubble;
	customEvent.cancelable = cancelable;
	customEvent.view = view;
	customEvent.detail = detail;
	customEvent.screenX = screenX;
	customEvent.screenY = screenY;
	customEvent.clientX = clientX;
	customEvent.clientY = clientY;
	customEvent.ctrlKey = ctrlKey;
	customEvent.altKey = altKey;
	customEvent.metaKey = metaKey;
	customEvent.shiftKey = shiftKey;

	//fix button property for IE's wacky implementation
	switch(button) {
		case 0:
			customEvent.button = 1;
			break;
		case 1:
			customEvent.button = 4;
			break;
		case 2:
			customEvent.button = 2;
			break;
		default:
			customEvent.button = 0;
	}

	/*
	* Have to use relatedTarget because IE won't allow assignment to toElement or fromElement on generic events. This
	*  keeps YAHOO.util.customEvent.getRelatedTarget() functional.
	*/
	customEvent.relatedTarget = relatedTarget;
	return customEvent;
};

/** @private */
Utils._mergeDefaultKeyEventArgumentsWithArgumentsMap = function(config) {
	return {
		canBubble : config.canBubble !== false ? true : false,
		cancelable : config.cancelable !== false ? true : false,
		view : config.view ? config.view : window,
		keyIdentifier : config.keyIdentifier ? config.keyIdentifier : 'undefined',
		modifiersListArg : config.modifiersListArg ? config.modifiersListArg : '',
		key : config.key ? config.key : 'undefined',
		ctrlKey : config.ctrlKey ? config.ctrlKey : false,
		altKey : config.altKey ? config.altKey : false,
		shiftKey : config.shiftKey ? config.shiftKey : false,
		metaKey : config.metaKey ? config.metaKey : false
	};
};

/** @private */
Utils._isClickEventWithNoEventOptions = function(eventString, element, options) {
	return eventString === 'click' && element.click && Utility.isEmpty(options);
};

/** @private */
Utils._isClickEventWithNoEventOptionsAndKOIsAvailable = function(eventString, element, options) {
	return Utils._isClickEventWithNoEventOptions(eventString, element, options) && presenter_knockout && presenter_knockout.utils;
};

module.exports = Utils;