define('js-test-utils/key-simulator', [
    'jquery',
    'lodash'
], function(
    $,
    _) {

    /**
     * @param options
     *
     * Options:
     * @param input     REQUIRED - target input, or a function that returns such input.
     * @param tick      OPTIONAL - a function that, when called, advances the clock (e.g. a fake timer) to simulate a pause
     */
    function KeySimulator(options) {
        this.options = options || {};
    }

    /**
     * Simulates one or more sequences, followed by a pause.
     * @param {string|number...} sequence - a string of text to append to the field, or a keyCode to trigger
     */
    KeySimulator.prototype.simulateTyping = function(/* sequence... */) {
        var $input = this._getInput();
        _.each(arguments, _.bind(function(sequence) {
            if (typeof sequence === 'string') {
                $input.val($input.val() + sequence);
                this.simulateKey();
            } else {
                this.simulateKey(sequence);
            }
        }, this));
        this.simulatePause();
    };

    KeySimulator.prototype.simulateKey = function(keyCode) {
        var $input = this._getInput();
        $input.trigger(this._keydownEvent(keyCode));
        $input.trigger(this._keyupEvent(keyCode));
    };

    KeySimulator.prototype.simulatePause = function() {
        this.options.tick && this.options.tick.call(null);
    };

    KeySimulator.prototype._keydownEvent = function(keyCode) {
        return $.Event('keydown', {
            keyCode: keyCode,
            which: keyCode
        });
    };

    KeySimulator.prototype._keyupEvent = function(keyCode) {
        return $.Event('keyup', {
            keyCode: keyCode,
            which: keyCode
        });
    };

    KeySimulator.prototype._getInput = function() {
        var input = this.options.input;
        return $(typeof input === 'function' ? input() : input);
    };

    return KeySimulator;
});