<commit_before>const Comparator = require('./util/Comparator')

/**
 * @class {NilXj4AxRgY}
 */
class NilXj4AxRgY {

}

/**
 * @class {SkipList}
 */
class SkipList {
    /**
     * @constructor
     *
     * @param {Function} compareFn
     * @param {Number} maxHeight
     * @param {Number} promotingProbability
     *
     * @example
     *
     * const SkipList = require('estructura/skip-list')
     *
     * const skipList = new SkipList( 16, .5 )
     */
    constructor(compareFn = Comparator.compareFnAscending, maxHeight = 32, promotingProbability = .5) {
        this.pHead_ = {
            aValue: new NilXj4AxRgY,

            rNode: null,
            dwnArr: []
        }

        this.compareFn_ = compareFn

        this.curHeight_ = 0
        this.maxHeight_ = maxHeight

        this.promotingProbability_ = promotingProbability

        this.size = 0

        this.peekFront = this.peek
    }

    /**
     * @private
     *
     * @return {Number}
     */
    getHeight_() {
        let lvlNr = 0

        while (Math.random() < this.promotingProbability_) lvlNr++

        return Math.min(Math.min(lvlNr, this.curHeight_ + 1), this.maxHeight_)
    }

    /**
     * Insert a new value within the Skip List.
     *
     * @param {*} aValue
     *
     * @return {Number}
     *
     * @example
     *
     * const skipList = new SkipList()
     *
     * skipList.insert('Jane Doe')
     * >>> 1
     */
    insert(aValue) {
        let curHght = this.curHeight_

        let prvNode = null
        let lftNode = this.pHead_

        const lMost = []

        const compareFn = this.compareFn_

        while (lftNode) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    lMost.push(lftNode)

                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }
            }
            else if (compareFn(aValue, lftNode.aValue) > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lMost.push(prvNode)

                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                lMost.push(prvNode)

                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        let insertAt = this.getHeight_()

        lMost.reverse()

        if (insertAt > this.curHeight_) {
            this.pHead_ = {
                aValue: this.pHead_.aValue,

                rNode: null,
                dwnArr: this.pHead_.dwnArr.slice().concat([this.pHead_])
            }

            this.curHeight_ = insertAt

            lMost.push(this.pHead_)
        }

        const dwnArr = []

        for (let lvlNr = 0, maxNr = insertAt; lvlNr <= maxNr; lvlNr++) {
            const lNode = lMost[lvlNr], rNode = {
                aValue: aValue,

                rNode: lNode.rNode,
                dwnArr: dwnArr.slice()
            }

            lNode.rNode = rNode

            dwnArr.push(rNode)
        }

        return ++this.size
    }

    /**
     * Search a value within the Skip List.
     *
     * Average O(log n), Worst Case O(n)
     *
     * @param {*} aValue
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('John Doe')
     * skipList.insert('Jane Doe')
     *
     * skipList.search('Jane Doe')
     * >>> Jane Doe
     */
    search(aValue) {
        let prvNode = null
        let lftNode = this.pHead_

        let curHght = this.curHeight_

        let searchValue = undefined

        while (lftNode && !searchValue) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }

                continue
            }

            const compareOu = this.compareFn_(aValue, lftNode.aValue)

            if (compareOu === 0) {
                searchValue = lftNode.aValue
            }
            else if (compareOu > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        return searchValue
    }

    /**
     * Remove a value from the Skip List.
     *
     * Average O(log n), Worst Case O(n)
     *
     * @param {*} aValue
     *
     * @return {Boolean}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('Jane Doe')
     * skipList.insert('John Doe')
     *
     * skipList.delete('John Doe')
     * >>> true
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('John Doe')
     *
     * skipList.delete('Jane Doe')
     * >>> false
     */
    delete(aValue) {
        let prvNode = null
        let lftNode = this.pHead_

        let curHght = this.curHeight_

        let valueDeleted = false

        while (lftNode && valueDeleted === false) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }

                continue
            }

            const compareOu = this.compareFn_(aValue, lftNode.aValue)

            if (compareOu > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                if (compareOu === 0) {
                    prvNode.rNode = lftNode.rNode
                    lftNode.rNode = null

                    if (curHght < 1) valueDeleted = true, this.size--
                }

                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        return valueDeleted

        /**
         * (!) Performance vs. Memory
         *
         * If we keep a lNode for each value, then it should be possible to lftNode.dwnArr instead of iterating through
         * each level.
         *
         */
    }

    /**
     * Take a look at the first value within the Skip List.
     *
     * `O(1)`
     *
     * @return {*}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('D')
     * skipList.insert('A')
     * skipList.insert('C')
     * skipList.insert('B')
     *
     * skipList.peek()
     * >>> A
     */
    peek() {
        const fstNode = (this.pHead_.dwnArr.length > 0 ? this.pHead_.dwnArr[0] : this.pHead_).rNode

        return fstNode ? fstNode.aValue : undefined
    }

    /**
     * Convert the Skip List to an array.
     *
     * `O(n)`
     *
     * @return {Array.<*>}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('A')
     * skipList.insert('C')
     * skipList.insert('D')
     * skipList.insert('B')
     *
     * skipList.toArray()
     * >>> [ "A", "B", "C", "D" ]
     */
    toArray() {
        const toArray = []

        let lNode = (this.pHead_.dwnArr.length > 0 ? this.pHead_.dwnArr[0] : this.pHead_).rNode

        while (lNode) {
            toArray.push(lNode.aValue)

            lNode = lNode.rNode
        }

        return toArray
    }

    /**
     * Create a Skip List from an Iterable.
     *
     * @param {Iterable} iterateOver
     * @param {Function} compareFn
     *
     * @return {SkipList}
     *
     * @example
     *
     * const skipList = SkipList.from(['A', 'C', 'D', 'B'])
     */
    static from(iterateOver, compareFn = Comparator.compareFnAscending) {
        const skipList = new SkipList(compareFn)

        for (const aValue of iterateOver) skipList.insert(aValue)

        return skipList
    }

    /*for (let lvlNr = 0, maxNr = this.curHeight_; lvlNr < maxNr; lvlNr++) {
        let rNode = (this.pHead_.dwnArr[lvlNr] || this.pHead_).rNode, arrLength = 0

        while (rNode) {
            arrLength++

            rNode = rNode.rNode
        }

        console.log('#.toArray', '\n', 'At', lvlNr, 'with', arrLength, 'item(s).')
    }*/
}

module.exports = SkipList<commit_msg>Average Case for the Skip List<commit_after>const Comparator = require('./util/Comparator')

/**
 * @class {NilXj4AxRgY}
 */
class NilXj4AxRgY {

}

/**
 * @class {SkipList}
 */
class SkipList {
    /**
     * @constructor
     *
     * @param {Function} compareFn
     * @param {Number} maxHeight
     * @param {Number} promotingProbability
     *
     * @example
     *
     * const SkipList = require('estructura/skip-list')
     *
     * const skipList = new SkipList( 16, .5 )
     */
    constructor(compareFn = Comparator.compareFnAscending, maxHeight = 32, promotingProbability = .5) {
        this.pHead_ = {
            aValue: new NilXj4AxRgY,

            rNode: null,
            dwnArr: []
        }

        this.compareFn_ = compareFn

        this.curHeight_ = 0
        this.maxHeight_ = maxHeight

        this.promotingProbability_ = promotingProbability

        this.size = 0

        this.peekFront = this.peek
    }

    /**
     * @private
     *
     * @return {Number}
     */
    getHeight_() {
        let lvlNr = 0

        while (Math.random() < this.promotingProbability_) lvlNr++

        return Math.min(Math.min(lvlNr, this.curHeight_ + 1), this.maxHeight_)
    }

    /**
     * Insert a new value within the Skip List.
     *
     * @param {*} aValue
     *
     * @return {Number}
     *
     * @example
     *
     * const skipList = new SkipList()
     *
     * skipList.insert('Jane Doe')
     * >>> 1
     */
    insert(aValue) {
        let curHght = this.curHeight_

        let prvNode = null
        let lftNode = this.pHead_

        const lMost = []

        const compareFn = this.compareFn_

        while (lftNode) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    lMost.push(lftNode)

                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }
            }
            else if (compareFn(aValue, lftNode.aValue) > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lMost.push(prvNode)

                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                lMost.push(prvNode)

                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        let insertAt = this.getHeight_()

        lMost.reverse()

        if (insertAt > this.curHeight_) {
            this.pHead_ = {
                aValue: this.pHead_.aValue,

                rNode: null,
                dwnArr: this.pHead_.dwnArr.slice().concat([this.pHead_])
            }

            this.curHeight_ = insertAt

            lMost.push(this.pHead_)
        }

        const dwnArr = []

        for (let lvlNr = 0, maxNr = insertAt; lvlNr <= maxNr; lvlNr++) {
            const lNode = lMost[lvlNr], rNode = {
                aValue: aValue,

                rNode: lNode.rNode,
                dwnArr: dwnArr.slice()
            }

            lNode.rNode = rNode

            dwnArr.push(rNode)
        }

        return ++this.size
    }

    /**
     * Search a value within the Skip List.
     *
     * Average O(log n), Worst Case O(n)
     *
     * p = this.promotingProbability_
     *
     * 1 + [ ( log 1 / p ( this.size ) ) / p ] + 1 / ( 1 - p )
     *
     * p = .5
     * this.size = 65 536
     *
     * 1 + [ log2( 65 536 ) / p   ] + 1 / ( 1 - p  )
     * 1 + [ log2( 65 536 ) / 0.5 ] + 1 / ( 1 - .5 )
     *
     * = 35
     *
     * p = .25
     * this.size = 65 536
     *
     * 1 + [ log4( 65536 ) / 0.25 ] + 1 / ( 1 - 0.25 )
     *
     * = 103 / 3
     * = 34.3333
     *
     * @see https://www.csee.umbc.edu/courses/undergraduate/341/fall01/Lectures/SkipLists/skip_lists/skip_lists.html
     *
     * @param {*} aValue
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('John Doe')
     * skipList.insert('Jane Doe')
     *
     * skipList.search('Jane Doe')
     * >>> Jane Doe
     */
    search(aValue) {
        let prvNode = null
        let lftNode = this.pHead_

        let curHght = this.curHeight_

        let searchValue = undefined

        while (lftNode && !searchValue) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }

                continue
            }

            const compareOu = this.compareFn_(aValue, lftNode.aValue)

            if (compareOu === 0) {
                searchValue = lftNode.aValue
            }
            else if (compareOu > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        return searchValue
    }

    /**
     * Remove a value from the Skip List.
     *
     * Average O(log n), Worst Case O(n)
     *
     * @param {*} aValue
     *
     * @return {Boolean}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('Jane Doe')
     * skipList.insert('John Doe')
     *
     * skipList.delete('John Doe')
     * >>> true
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('John Doe')
     *
     * skipList.delete('Jane Doe')
     * >>> false
     */
    delete(aValue) {
        let prvNode = null
        let lftNode = this.pHead_

        let curHght = this.curHeight_

        let valueDeleted = false

        while (lftNode && valueDeleted === false) {
            if (lftNode.aValue instanceof NilXj4AxRgY) {
                if (lftNode.rNode) {
                    prvNode = lftNode
                    lftNode = prvNode.rNode
                }
                else {
                    prvNode = null
                    lftNode = lftNode.dwnArr[--curHght]
                }

                continue
            }

            const compareOu = this.compareFn_(aValue, lftNode.aValue)

            if (compareOu > 0) {
                prvNode = lftNode
                lftNode = prvNode.rNode

                if (!lftNode) {
                    lftNode = prvNode.dwnArr[--curHght]
                    prvNode = null
                }
            }
            else {
                if (compareOu === 0) {
                    prvNode.rNode = lftNode.rNode
                    lftNode.rNode = null

                    if (curHght < 1) valueDeleted = true, this.size--
                }

                lftNode = prvNode.dwnArr[--curHght]
                prvNode = null
            }
        }

        return valueDeleted

        /**
         * (!) Performance vs. Memory
         *
         * If we keep a lNode for each value, then it should be possible to lftNode.dwnArr instead of iterating through
         * each level.
         *
         */
    }

    /**
     * Take a look at the first value within the Skip List.
     *
     * `O(1)`
     *
     * @return {*}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('D')
     * skipList.insert('A')
     * skipList.insert('C')
     * skipList.insert('B')
     *
     * skipList.peek()
     * >>> A
     */
    peek() {
        const fstNode = (this.pHead_.dwnArr.length > 0 ? this.pHead_.dwnArr[0] : this.pHead_).rNode

        return fstNode ? fstNode.aValue : undefined
    }

    /**
     * Convert the Skip List to an array.
     *
     * `O(n)`
     *
     * @return {Array.<*>}
     *
     * @example
     *
     * const skipList = new SkipList()
     * skipList.insert('A')
     * skipList.insert('C')
     * skipList.insert('D')
     * skipList.insert('B')
     *
     * skipList.toArray()
     * >>> [ "A", "B", "C", "D" ]
     */
    toArray() {
        const toArray = []

        let lNode = (this.pHead_.dwnArr.length > 0 ? this.pHead_.dwnArr[0] : this.pHead_).rNode

        while (lNode) {
            toArray.push(lNode.aValue)

            lNode = lNode.rNode
        }

        return toArray
    }

    /**
     * Create a Skip List from an Iterable.
     *
     * @param {Iterable} iterateOver
     * @param {Function} compareFn
     *
     * @return {SkipList}
     *
     * @example
     *
     * const skipList = SkipList.from(['A', 'C', 'D', 'B'])
     */
    static from(iterateOver, compareFn = Comparator.compareFnAscending) {
        const skipList = new SkipList(compareFn)

        for (const aValue of iterateOver) skipList.insert(aValue)

        return skipList
    }

    /*for (let lvlNr = 0, maxNr = this.curHeight_; lvlNr < maxNr; lvlNr++) {
        let rNode = (this.pHead_.dwnArr[lvlNr] || this.pHead_).rNode, arrLength = 0

        while (rNode) {
            arrLength++

            rNode = rNode.rNode
        }

        console.log('#.toArray', '\n', 'At', lvlNr, 'with', arrLength, 'item(s).')
    }*/
}

module.exports = SkipList<|endoftext|>


<commit_before>(function(global) {

  'use strict';

  var config = {
    username: '',
    password: '',
    apikey: '',
    hostname: 'localhost',
    port: 9595,
    secure: false
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = config;
  else
    global.config = config;

})(this);
<commit_msg>hostname was renamed host<commit_after>(function(global) {

  'use strict';

  var config = {
    username: '',
    password: '',
    apikey: '',
    host: 'localhost',
    port: 9595,
    secure: false
  };

  if (typeof module !== 'undefined' && module.exports)
    module.exports = config;
  else
    global.config = config;

})(this);
<|endoftext|>


<commit_before>"use strict";

var Bus = {};

if(typeof window === "object")
{
    window["Bus"] = Bus;
}

/** @constructor */
Bus.Connector = function()
{
    this.listeners = {};
    this.pair = undefined;
};

Bus.Connector.prototype.register = function(name, fn, thisValue)
{
    var listeners = this.listeners[name];

    if(listeners === undefined)
    {
        listeners = this.listeners[name] = [];
    }

    listeners.push({
        fn: fn,
        thisValue: thisValue,
    });
};

/**
 * Send ("emit") a message
 *
 * @param {string} name
 * @param {Object=} value
 */
Bus.Connector.prototype.send = function(name, value)
{
    dbg_assert(arguments.length === 1 || arguments.length === 2);

    if(!this.pair)
    {
        return;
    }

    var listeners = this.pair.listeners[name];

    if(listeners === undefined)
    {
        return;
    }

    for(var i = 0; i < listeners.length; i++)
    {
        var listener = listeners[i];
        listener.fn.call(listener.thisValue, value);
    }
};

/**
 * Send a message, guaranteeing that it is received asynchronously
 *
 * @param {string} name
 * @param {Object=} value
 */
Bus.Connector.prototype.send_async = function(name, value)
{
    dbg_assert(arguments.length === 1 || arguments.length === 2);

    setTimeout(this.send.bind(this, name, value), 0);
};

Bus.create = function()
{
    var c0 = new Bus.Connector();
    var c1 = new Bus.Connector();

    c0.pair = c1;
    c1.pair = c0;

    return [c0, c1];
};

<commit_msg>bus.should_send<commit_after>"use strict";

var Bus = {};

/** @constructor */
Bus.Connector = function()
{
    this.listeners = {};
    this.pair = undefined;
};

Bus.Connector.prototype.register = function(name, fn, thisValue)
{
    var listeners = this.listeners[name];

    if(listeners === undefined)
    {
        listeners = this.listeners[name] = [];
    }

    listeners.push({
        fn: fn,
        thisValue: thisValue,
    });
};

/**
 * Send ("emit") a message
 *
 * @param {string} name
 * @param {Object=} value
 */
Bus.Connector.prototype.send = function(name, value)
{
    dbg_assert(arguments.length === 1 || arguments.length === 2);

    if(!this.pair)
    {
        return;
    }

    var listeners = this.pair.listeners[name];

    if(listeners === undefined)
    {
        return;
    }

    for(var i = 0; i < listeners.length; i++)
    {
        var listener = listeners[i];
        listener.fn.call(listener.thisValue, value);
    }
};

/**
 * Send a message, guaranteeing that it is received asynchronously
 *
 * @param {string} name
 * @param {Object=} value
 */
Bus.Connector.prototype.send_async = function(name, value)
{
    dbg_assert(arguments.length === 1 || arguments.length === 2);

    setTimeout(this.send.bind(this, name, value), 0);
};

/**
 * Return true if a message with the given name should be sent. That is, if
 * there are any listeners for this message
 *
 * @param {string} name
 * @return {boolean}
 */
Bus.Connector.prototype.should_send = function(name)
{
    if(!this.pair)
    {
        return false;
    }

    var listeners = this.pair.listeners[name];

    return listeners !== undefined && listeners.length > 0;
};

Bus.create = function()
{
    var c0 = new Bus.Connector();
    var c1 = new Bus.Connector();

    c0.pair = c1;
    c1.pair = c0;

    return [c0, c1];
};

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  refines: 'foam.core.StubMethod',

  methods: [
    function buildJavaClass(cls) {
      if ( ! this.javaSupport ) return;

      var name = this.name;
      var args = this.args;
      var boxPropName = foam.String.capitalize(this.boxPropName);
      var replyPolicyName = foam.String.capitalize(this.replyPolicyName);

      var code =
`foam.box.Message message = getX().create(foam.box.Message.class);
foam.box.RPCMessage rpc = getX().create(foam.box.RPCMessage.class);
rpc.setName("${name}");
Object[] args = { ${ args.map( a => a.name ).join(',') } };
rpc.setArgs(args);

message.setObject(rpc);
foam.box.RPCReturnBox replyBox = getX().create(foam.box.RPCReturnBox.class);
message.getAttributes().put("replyBox", replyBox);
get${boxPropName}().send(message);
try {
  replyBox.getSemaphore().acquire();
} catch (Throwable t) {
  throw new RuntimeException(t);
}

Object result = replyBox.getMessage().getObject();`;

      if ( this.javaReturns && this.javaReturns !== 'void' ) {
        code += `if ( result instanceof foam.box.RPCReturnMessage )
  return (${this.javaReturns})((foam.box.RPCReturnMessage)result).getData();
`;
}

code += `if ( result instanceof java.lang.Throwable )
  throw new RuntimeException((java.lang.Throwable)result);

if ( result instanceof foam.box.RPCErrorMessage )
  throw new RuntimeException(((foam.box.RPCErrorMessage)result).getData().toString());

throw new RuntimeException("Invalid repsonse type: " + result.getClass());
`;

      this.javaCode = code;

      this.SUPER(cls);
    }
  ]
});
<commit_msg>Added logic to only throw Invalid response exception if javaReturns is not void<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  refines: 'foam.core.StubMethod',

  methods: [
    function buildJavaClass(cls) {
      if ( ! this.javaSupport ) return;

      var name = this.name;
      var args = this.args;
      var boxPropName = foam.String.capitalize(this.boxPropName);
      var replyPolicyName = foam.String.capitalize(this.replyPolicyName);

      var code =
`foam.box.Message message = getX().create(foam.box.Message.class);
foam.box.RPCMessage rpc = getX().create(foam.box.RPCMessage.class);
rpc.setName("${name}");
Object[] args = { ${ args.map( a => a.name ).join(',') } };
rpc.setArgs(args);

message.setObject(rpc);
foam.box.RPCReturnBox replyBox = getX().create(foam.box.RPCReturnBox.class);
message.getAttributes().put("replyBox", replyBox);
get${boxPropName}().send(message);
try {
  replyBox.getSemaphore().acquire();
} catch (Throwable t) {
  throw new RuntimeException(t);
}

Object result = replyBox.getMessage().getObject();
`;

      if ( this.javaReturns && this.javaReturns !== 'void' ) {
        code += `if ( result instanceof foam.box.RPCReturnMessage )
  return (${this.javaReturns})((foam.box.RPCReturnMessage)result).getData();
`;
}

code += `if ( result instanceof java.lang.Throwable )
  throw new RuntimeException((java.lang.Throwable)result);

if ( result instanceof foam.box.RPCErrorMessage )
  throw new RuntimeException(((foam.box.RPCErrorMessage)result).getData().toString());
`;

      if ( this.javaReturns && this.javaReturns !== 'void') {
        code += `throw new RuntimeException("Invalid response type: " + result.getClass());`;
      }

      this.javaCode = code;

      this.SUPER(cls);
    }
  ]
});
<|endoftext|>


<commit_before>const { setWorldConstructor, setDefaultTimeout, Given, When, Then } = require('cucumber')
const path = require('path')
const os = require('os')
const childProcess = require('child_process')
const { promisify } = require('util')
const isTravis = require('is-travis')
const Application = require('spectron').Application
const $ = require('cheerio')
const jetpack = require('fs-jetpack')
const { git, clone } = require('../../app/lib/git')
const exec = promisify(childProcess.exec)

const appPath = path.join(__dirname, '../../', 'app/background.js')
const homeDir = path.join(__dirname, '../../test/fixtures/home')
const pluginDir = path.join(homeDir, '.zazu/plugins')
const calcProfile = path.join(homeDir, '.calculator.zazurc.json')
const fallbackProfile = path.join(homeDir, '.fallback.zazurc.json')
const homeProfile = path.join(homeDir, '.zazurc.json')

setDefaultTimeout(60 * 1000)

class World {
  profile (name) {
    this.profileType = name
    if (this.profileType === 'calculator') {
      jetpack.copy(calcProfile, homeProfile, { overwrite: true })
    } else if (this.profileType === 'fallback') {
      jetpack.copy(fallbackProfile, homeProfile, { overwrite: true })
    }
    this.app = new Application({
      path: require('electron'),
      args: [appPath],
      env: {
        NODE_ENV: 'test',
        ZAZU_HOME: homeDir,
      },
    })
    return Promise.resolve()
  }

  async open () {
    await this.app.start()
    const time = this.profileType === 'calculator' ? 50 * 1000 : 5 * 1000
    return wait(time) // give it time to load plugins
  }

  isWindowVisible () {
    return this.app.browserWindow.isVisible()
  }

  hasResults () {
    return this.app.client.isExisting('.results')
  }

  accessibility () {
    return this.app.client.auditAccessibility()
  }

  updatePlugins () {
    return this.app.webContents.send('updatePlugins').then(() => {
      return wait(10 * 1000) // give it time to update the plugin
    })
  }

  getQuery () {
    return this.app.client.getValue('input')
  }

  async type (input) {
    await wait(500)
    for (const char of input) {
      await this.hitKey(char)
    }
    await wait(500)
  }

  async showWindow () {
    await this.hitKey('space', 'shift')
    await wait(500)
  }

  hideWindow () {
    return this.hitKey('space', 'shift')
  }

  hitKey (key, modifier) {
    if (os.type() === 'Darwin' && !isTravis) {
      const keyForAppleScript = key.length === 1 ? `\\"${key}\\"` : key
      if (modifier) {
        const modifierForAppleScript = modifier.replace('alt', 'option')
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript} using ${modifierForAppleScript} down"
          osascript -e "$Script"`)
      } else {
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript}"
          osascript -e "$Script"`)
      }
    }
    const robot = require('robotjs')
    if (modifier) {
      return Promise.resolve(robot.keyTap(key, modifier))
    } else {
      // * will becomes 8, see https://github.com/octalmage/robotjs/issues/285
      const robotjsBadChars = /[~!@#$%^&*()_+{}|:"<>?]/
      let match = key.match(robotjsBadChars)
      if (match) {
        return Promise.resolve(robot.keyTap(key, 'shift'))
      }
      return Promise.resolve(robot.keyTap(key))
    }
  }

  close () {
    if (this.app) {
      return this.app.stop()
    }
    return Promise.resolve()
  }

  clickActiveResult () {
    return this.app.client.click('li.active')
  }

  getActiveHeader () {
    return this.app.client.getText('li.active h2')
  }

  getResults () {
    return this.app.client.getText('.results')
  }

  getResultItems () {
    return this.app.client.getHTML('.results').then(results => {
      return $(results).find('li')
    })
  }

  readClipboard () {
    return this.app.electron.clipboard.readText()
  }
}

const wait = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

const eventually = (func, expectedValue) => {
  const assert = actualValue => {
    if (actualValue !== expectedValue) {
      throw new Error(`Values didn't match`)
    }
  }
  return new Promise((resolve, reject) => {
    let iterations = 0
    const retry = () => {
      iterations++
      if (iterations >= 50) {
        reject(new Error('Forever is a long time'))
      } else {
        func()
          .then(assert)
          .then(resolve)
          .catch(() => {
            return wait(100).then(retry)
          })
      }
    }
    retry()
  })
}

setWorldConstructor(World)

Given('I have {string} as a plugin', function (plugin) {
  if (plugin === 'tinytacoteam/zazu-fixture') {
    return this.profile('default')
  } else if (plugin === 'tinytacoteam/zazu-calculator') {
    return this.profile('calculator')
  }
  return Promise.reject(new Error('Profile not found'))
})

Given('the app is launched', { timeout: 120 * 1000 }, async function () {
  await this.open()
})

Given('I have {string} installed before packagist support', function (plugin) {
  const fallbackDir = path.join(pluginDir, plugin)
  return clone(plugin, fallbackDir)
    .then(() => {
      return git(['reset', '--hard', '16e4e50'], { cwd: fallbackDir })
    })
    .then(() => {
      return this.profile('fallback')
    })
})

Given('I update the plugins', { timeout: 15 * 1000 }, function () {
  return this.updatePlugins()
})

When('I toggle it open', async function () {
  await this.showWindow()
  await wait(100)
})

When('I toggle it closed', function () {
  return this.hideWindow()
})

// assumes modifier is first
When('I hit the hotkey {string}', function (hotkey) {
  var keys = hotkey.split('+')
  return this.hitKey(keys[1], keys[0]).then(() => {
    return wait(100)
  })
})

When('I hit the key {string}', function (hotkey) {
  return this.hitKey(hotkey).then(() => {
    return wait(100)
  })
})

When('I eventually click on the active result', function () {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return this.clickActiveResult()
    })
})

Then('my clipboard contains {string}', function (expected, callback) {
  this.readClipboard().then(actual => {
    if (actual === expected) {
      callback()
    } else {
      callback(new Error('Expected "' + expected + '" to be in your clipbaord but found "' + actual + '"'))
    }
  })
})

Then('the search window is not visible', function () {
  return eventually(() => this.isWindowVisible(), false)
})

Then('the search window is visible', function () {
  return eventually(() => this.isWindowVisible(), true)
})

Then('I have {int} result(s)', function (expected) {
  return eventually(() => {
    return this.getResultItems().then(items => items.length)
  }, parseInt(expected, 10))
})

Then('the input is empty', function () {
  return eventually(() => {
    return this.getQuery()
  }, '')
})

Then('the input is {string}', function (expected) {
  return eventually(() => {
    return this.getQuery()
  }, expected)
})

When('I type in {string}', async function (input) {
  await this.type(input)
  await eventually(() => {
    return this.getQuery()
  }, input)
})

Then('I have no results', function () {
  return eventually(() => this.hasResults(), false)
})

Then('the active result contains {string}', function (header) {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return eventually(() => this.getActiveHeader(), header)
    })
})

Then('I have no accessibility warnings', function () {
  return this.accessibility().then(response => {
    if (response.results.length !== 0) {
      throw new Error('You have accessibility issues')
    }
  })
})

Then('the results contain {string}', function (subset, callback) {
  this.getResults().then(resultText => {
    if (resultText.match(subset)) {
      callback()
    } else {
      callback(new Error('Expected results to contain "' + subset + '"'))
    }
  })
})
<commit_msg>prevent debounce in test<commit_after>const { setWorldConstructor, setDefaultTimeout, Given, When, Then } = require('cucumber')
const path = require('path')
const os = require('os')
const childProcess = require('child_process')
const { promisify } = require('util')
const isTravis = require('is-travis')
const Application = require('spectron').Application
const $ = require('cheerio')
const jetpack = require('fs-jetpack')
const { git, clone } = require('../../app/lib/git')
const exec = promisify(childProcess.exec)

const appPath = path.join(__dirname, '../../', 'app/background.js')
const homeDir = path.join(__dirname, '../../test/fixtures/home')
const pluginDir = path.join(homeDir, '.zazu/plugins')
const calcProfile = path.join(homeDir, '.calculator.zazurc.json')
const fallbackProfile = path.join(homeDir, '.fallback.zazurc.json')
const homeProfile = path.join(homeDir, '.zazurc.json')

setDefaultTimeout(60 * 1000)

class World {
  profile (name) {
    this.profileType = name
    if (this.profileType === 'calculator') {
      jetpack.copy(calcProfile, homeProfile, { overwrite: true })
    } else if (this.profileType === 'fallback') {
      jetpack.copy(fallbackProfile, homeProfile, { overwrite: true })
    }
    this.app = new Application({
      path: require('electron'),
      args: [appPath],
      env: {
        NODE_ENV: 'test',
        ZAZU_HOME: homeDir,
      },
    })
    return Promise.resolve()
  }

  async open () {
    await this.app.start()
    const time = this.profileType === 'calculator' ? 50 * 1000 : 5 * 1000
    return wait(time) // give it time to load plugins
  }

  isWindowVisible () {
    return this.app.browserWindow.isVisible()
  }

  hasResults () {
    return this.app.client.isExisting('.results')
  }

  accessibility () {
    return this.app.client.auditAccessibility()
  }

  updatePlugins () {
    return this.app.webContents.send('updatePlugins').then(() => {
      return wait(10 * 1000) // give it time to update the plugin
    })
  }

  getQuery () {
    return this.app.client.getValue('input')
  }

  async type (input) {
    await wait(500)
    for (const char of input) {
      await this.hitKey(char)
      await wait(110)
    }
    await wait(500)
  }

  async showWindow () {
    await this.hitKey('space', 'shift')
    await wait(500)
  }

  hideWindow () {
    return this.hitKey('space', 'shift')
  }

  hitKey (key, modifier) {
    if (os.type() === 'Darwin' && !isTravis) {
      const keyForAppleScript = key.length === 1 ? `\\"${key}\\"` : key
      if (modifier) {
        const modifierForAppleScript = modifier.replace('alt', 'option')
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript} using ${modifierForAppleScript} down"
          osascript -e "$Script"`)
      } else {
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript}"
          osascript -e "$Script"`)
      }
    }
    const robot = require('robotjs')
    if (modifier) {
      return Promise.resolve(robot.keyTap(key, modifier))
    } else {
      // * will becomes 8, see https://github.com/octalmage/robotjs/issues/285
      const robotjsBadChars = /[~!@#$%^&*()_+{}|:"<>?]/
      let match = key.match(robotjsBadChars)
      if (match) {
        return Promise.resolve(robot.keyTap(key, 'shift'))
      }
      return Promise.resolve(robot.keyTap(key))
    }
  }

  close () {
    if (this.app) {
      return this.app.stop()
    }
    return Promise.resolve()
  }

  clickActiveResult () {
    return this.app.client.click('li.active')
  }

  getActiveHeader () {
    return this.app.client.getText('li.active h2')
  }

  getResults () {
    return this.app.client.getText('.results')
  }

  getResultItems () {
    return this.app.client.getHTML('.results').then(results => {
      return $(results).find('li')
    })
  }

  readClipboard () {
    return this.app.electron.clipboard.readText()
  }
}

const wait = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

const eventually = (func, expectedValue) => {
  const assert = actualValue => {
    if (actualValue !== expectedValue) {
      throw new Error(`Values didn't match`)
    }
  }
  return new Promise((resolve, reject) => {
    let iterations = 0
    const retry = () => {
      iterations++
      if (iterations >= 50) {
        reject(new Error('Forever is a long time'))
      } else {
        func()
          .then(assert)
          .then(resolve)
          .catch(() => {
            return wait(100).then(retry)
          })
      }
    }
    retry()
  })
}

setWorldConstructor(World)

Given('I have {string} as a plugin', function (plugin) {
  if (plugin === 'tinytacoteam/zazu-fixture') {
    return this.profile('default')
  } else if (plugin === 'tinytacoteam/zazu-calculator') {
    return this.profile('calculator')
  }
  return Promise.reject(new Error('Profile not found'))
})

Given('the app is launched', { timeout: 120 * 1000 }, async function () {
  await this.open()
})

Given('I have {string} installed before packagist support', function (plugin) {
  const fallbackDir = path.join(pluginDir, plugin)
  return clone(plugin, fallbackDir)
    .then(() => {
      return git(['reset', '--hard', '16e4e50'], { cwd: fallbackDir })
    })
    .then(() => {
      return this.profile('fallback')
    })
})

Given('I update the plugins', { timeout: 15 * 1000 }, function () {
  return this.updatePlugins()
})

When('I toggle it open', async function () {
  await this.showWindow()
  await wait(100)
})

When('I toggle it closed', function () {
  return this.hideWindow()
})

// assumes modifier is first
When('I hit the hotkey {string}', function (hotkey) {
  var keys = hotkey.split('+')
  return this.hitKey(keys[1], keys[0]).then(() => {
    return wait(100)
  })
})

When('I hit the key {string}', function (hotkey) {
  return this.hitKey(hotkey).then(() => {
    return wait(100)
  })
})

When('I eventually click on the active result', function () {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return this.clickActiveResult()
    })
})

Then('my clipboard contains {string}', function (expected, callback) {
  this.readClipboard().then(actual => {
    if (actual === expected) {
      callback()
    } else {
      callback(new Error('Expected "' + expected + '" to be in your clipbaord but found "' + actual + '"'))
    }
  })
})

Then('the search window is not visible', function () {
  return eventually(() => this.isWindowVisible(), false)
})

Then('the search window is visible', function () {
  return eventually(() => this.isWindowVisible(), true)
})

Then('I have {int} result(s)', function (expected) {
  return eventually(() => {
    return this.getResultItems().then(items => items.length)
  }, parseInt(expected, 10))
})

Then('the input is empty', function () {
  return eventually(() => {
    return this.getQuery()
  }, '')
})

Then('the input is {string}', function (expected) {
  return eventually(() => {
    return this.getQuery()
  }, expected)
})

When('I type in {string}', async function (input) {
  await this.type(input)
  await eventually(() => {
    return this.getQuery()
  }, input)
})

Then('I have no results', function () {
  return eventually(() => this.hasResults(), false)
})

Then('the active result contains {string}', function (header) {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return eventually(() => this.getActiveHeader(), header)
    })
})

Then('I have no accessibility warnings', function () {
  return this.accessibility().then(response => {
    if (response.results.length !== 0) {
      throw new Error('You have accessibility issues')
    }
  })
})

Then('the results contain {string}', function (subset, callback) {
  this.getResults().then(resultText => {
    if (resultText.match(subset)) {
      callback()
    } else {
      callback(new Error('Expected results to contain "' + subset + '"'))
    }
  })
})
<|endoftext|>


<commit_before>/**
 * Provides utilities for the system. (e.g. it loads documents, facets, tags and tooltip texts)
 *
 * @constructor
 * @this {Util}
 */
var Util = new function() {
	/* array for all documents metadata from the server */
	this.documents = [];
	/* array for all possible facets */
	this.facets = [];
	/* array of strings for all possible document views */
	// this.docTypes = ['outline', 'text', 'pages', 'thumbnails', 'images', 'tei', 'map', 'tags'];
	// change this to make the gui look other wise
	this.docTypes = [
		{
			type: 'text',
			icon: 'type'
		},
		{
			type: 'outline',
			icon: 'list-nested'
		},
		{
			type: 'pages',
			icon: 'file'
		},
		{
			type: 'thumbnails',
			icon: 'grid'
		},
		{
			type: 'images',
			icon: 'image'
		},
		{
			type: 'tei',
			icon: 'tree'
		},
		{
			type: 'map',
			icon: 'earth'
		},
		{
			type: 'tags',
			icon: 'cloud'
		}
	];
	this.docsLoaded = -1;
	this.facetsLoaded = false;
	this.texts = EditionTooltips;
	if (typeof navigator.language != 'undefined' && navigator.language.indexOf("de") > -1) {
		this.language = "de";
	}
	else
		if (typeof navigator.browserLanguage != 'undefined' && navigator.browserLanguage.indexOf("de") > -1) {
			this.language = "de";
		}
		else {
			this.language = "en";
		}
};

/**
 * Getter for an ID for a given document type. Required for generating magnetic links.
 *
 * @this {Util}
 * @param {string} type The document type.
 * @return {number} The corresponding document type id.
 */
Util.getIdByType = function(type) {
	for (var i = 0; i < this.docTypes.length; i++) {
		if (this.docTypes[i].type == type) {
			return i;
		}
	}
};

/**
 * Getter for the document type of a given ID. Required for utilizing magnetic links.
 *
 * @this {Util}
 * @param {number} id The id of the document type.
 * @return {string} The corresponding document type.
 */
Util.getTypeById = function(id) {
	return this.docTypes[id];
};

/**
 * Getter for a specific attribute of a given node. Required for attribute names with name spaces like 'xml:href'.
 *
 * @this {Util}
 * @param {XML} node The XML node to get the attribute.
 * @param {string} attributeName The attribute to search for.
 * @return {string} The value of the attribute.
 */
Util.getAttribute = function(node, attributeName) {
	var value = $(node).attr(attributeName);
	if (typeof value == 'undefined') {
		for (var i = 0; i < node.attributes.length; i++) {
			if (attributeName == node.attributes[i].name) {
				value = node.attributes[i].nodeValue;
				break;
			}
		}
	}
	return value;
};

/**
 * Getter for a language dependent tooltip string.
 *
 * @this {Util}
 * @param {string} id The id of the tooltip, e.g. 'folder'.
 * @return {string} The corresponding tooltip dependent on the applications language.
 */
Util.getString = function(id) {
	var label = '';

	try {
		label = Util.texts[id][Util.language];
	} catch (e) {
		// console.log(e);
	}
	return label;
};

/**
 * Asynchronous loader for all available documents. This function retrieves the metadata file for all documents. Afterwards, each single document gets retrieved.
 *
 * @this {Util}
 * @param {Object} trigger A trigger function to be called when a document has been loaded.
 */
Util.loadDocuments = function(trigger) {
	Util.docsLoaded = 0;
	var gui = this;
	var callback = function(xml) {
		var docs = $(xml).find('doc');
		var loadMets = function(document) {
			var imagePath, images = [];
			var metsCallback = function(xml) {
				$(xml).find('[nodeName="METS:mets"]').find('[nodeName="METS:fileSec"]').find('[nodeName="METS:fileGrp"]').first().find('[nodeName="METS:file"]').each(function() {
					var node = $(this).find('[nodeName="METS:FLocat"]')[0];
					var fullPath = Util.getAttribute(node, 'xlink:href');
					images.push(fullPath.substring(fullPath.lastIndexOf("/") + 1));
					if (!imagePath) {
						var dummy = fullPath.substring(0, fullPath.lastIndexOf("/"));
						imagePath = dummy.substring(0, dummy.lastIndexOf("/") + 1);
					}
				})
				document.imagePath = imagePath;
				document.images = images;
				Util.documents.push(document);
				if (Util.documents.length == docs.length) {
					Util.docsLoaded = 1;
				}
				if (typeof trigger != 'undefined') {
					trigger(document);
				}
			};
			DocumentServerConnection.getMets(document.title, true, metsCallback);
		}
		for (var i = 0; i < docs.length; i++) {

			var fullText = function() {
				return $(docs[i]).find('fulltext');
			}

			if (fullText()) {
				var document = new Document(
						$(docs[i]).find('id').text(),
						$(docs[i]).find('title').text(),
						$(docs[i]).find('titleShort').text(),
						$(docs[i]).find('preview').text(),
						$(docs[i]).find('pageCount').text(),
						fullText()
				);
				loadMets(document);
			}
		}
	}
	DocumentServerConnection.getDocuments(callback);
};

/**
 * Loads a single document synchronously. Used for utilizing magnetic links; the intention is to load related documents before unrelated ones.
 *
 * @this {Util}
 * @param {string} title The title (id) of the document.
 * @param {string} nameShort The short name of the document.
 */
Util.loadDocumentSync = function(title, nameShort) {
	var pageCount;

	var document = new Document(title, '', nameShort, '');
	var imagePath, images = [];
	var metsCallback = function(xml) {
		$(xml).find('[nodeName="METS:mets"]').find('[nodeName="METS:fileSec"]').find('[nodeName="METS:fileGrp"]').first().find('[nodeName="METS:file"]').each(function() {
			var node = $(this).find('[nodeName="METS:FLocat"]')[0];
			var fullPath = Util.getAttribute(node, 'xlink:href');
			images.push(fullPath.substring(fullPath.lastIndexOf("/") + 1));
			if (!imagePath) {
				var dummy = fullPath.substring(0, fullPath.lastIndexOf("/"));
				imagePath = dummy.substring(0, dummy.lastIndexOf("/") + 1);
			}
		})
		pageCount = $(xml).find('body').find('div').last().find('pb').last().attr('n')
		console.log($(xml));
		document.pages = pageCount;
		document.imagePath = imagePath;
		document.images = images;
	};
	DocumentServerConnection.getMets(document.title, false, metsCallback);
	return document;
};

/**
 * Getter for a document with a specific title. Currently used from the external Scrips page.
 *
 * @this {Util}
 * @param {string} title The title (id) of the document.
 * @return {Document} The document with the given title.
 */
Util.getDoc = function(title) {
	for (var i in Util.documents) {
		if (Util.documents[i].title == title) {
			return Util.documents[i];
		}
	}
};

/**
 * Loader for the possible facets of a document. Finally, we receive 2 types of facets:
 * 1) facets for rendering in texts, e.g. 'tei:placeName' for place names
 * 2) facets for additional information for documents, e.g. 'tei:note' for notes for a document
 *
 * @this {Util}
 * @param {Object} trigger A trigger function to be called when the facets have been loaded.
 */
Util.loadFacets = function(trigger) {
	var i = 0;
	var callback = function(xml) {
		$(xml).find('[nodeName="tei:tei"]').children().each(function() {
			var labels = [];
			$(this).find('foreign').each(function() {
				labels.push({
								language: Util.getAttribute(this, 'xml:lang'),
								label: $(this).text()
							});
			});
			var render = $(this).attr("rend");
			var color = undefined;
			if (render) {
				var dummy = $("
").appendTo('body');
				color = $(dummy).css('color');
				$(dummy).remove();
				i++;
			}
			var facet = {
				facet: this.nodeName,
				render: render,
				labels: labels,
				color: color
			};
			Util.facets.push(facet);
			if (typeof trigger != 'undefined' && render) {
				trigger(facet);
			}
		});
		Util.facetsLoaded = true;
	}
	DocumentServerConnection.getFacets(callback);
};

/**
 * Getter for a label of a facet in the proper language.
 *
 * @this {Util}
 * @param {JSON} facet The facet to get the label.
 * @return {string} The label of the facet in the browsers language.
 */
Util.getFacetLabel = function(facet) {
	for (var i in facet.labels) {
		if (facet.labels[i].language == Util.language) {
			return facet.labels[i].label;
		}
	}
};

/**
 * Getter for a facet by a given id.
 *
 * @this {Util}
 * @param {string} The id of the facet, e.g. 'tei:placeName'.
 * @return {JSON} The JSON data of the corresponding facet.
 */
Util.getFacet = function(id) {
	for (var i in Util.facets) {
		if (Util.facets[i].facet == id) {
			return Util.facets[i];
		}
	}
};

/**
 * Getter for a specific number (EditionProperties.maxTags) of tags from the given xml file.
 *
 * @this {Util}
 * @param {XML} tags The xml file containing the tag data.
 * @return {Array} A specific number of most frequent tags.
 */
Util.getTags = function(tags) {
	var tagArray = [];
	for (var i = 0; i < tags.length; i++) {
		var text = $(tags[i]).find('tag').text();
		if (text == "") {
			continue;
		}
		var weight = $(tags[i]).find('count').text();
		var url = $(tags[i]).find('link').text();
		var facet = Util.getFacet($(tags[i]).find('facet').text());
		var tooltip = weight + " " + Util.getString('occurences');
		var link = '' + text + '';
		tagArray.push({
						  text: link,
						  weight: weight
					  });
		if (EditionProperties.maxTags && EditionProperties.maxTags == tagArray.length) {
			break;
		}
	}
	return tagArray;
};

/**
 * Returns the HTML for an error message of a given type; used to communicate AJAX response errors when loading data from the server.
 *
 * @this {Util}
 * @param {number} errorType The error id (e.g. 404).
 * @return {HTML} The error message content DIV.
 */
Util.getErrorMessage = function(errorType) {
	var errorDiv = $("
");
	$("
" + Util.getString('errorMessage') + ' (' + errorType + ')' + "
").appendTo(errorDiv);
	return errorDiv;
};

/**
 * Returns the HTML for an alert message. Used to communicate if the user needs to select facets when required.
 *
 * @this {Util}
 * @param {string} message The alert message to be shown.
 * @return {HTML} The alert message content DIV.
 */
Util.getAlertMessage = function(message) {
	var alertDiv = $("
");
	$("
" + message + "
").appendTo(alertDiv);
	return alertDiv;
};

/**
 * Used to transform Ascii to HEX code for generating working hyperlinks.
 *
 * @this {Util}
 * @param {string} text The text to transform.
 * @return {string} The transformed hex code.
 */
Util.asciiToHex = function(text) {
	var ascii = new Array("!", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "/", ":", ";", "=", "?", "@", "[", "]", " ");
	var hex = new Array("%21", "%23", "%24", "!$#", "%26", "%27", "%28", "%29", "%2a", "%2b", "%2c", "%2f", "%3a", "%3b", "%3d", "%3f", "%40", "%5b", "%5d", "%20");
	for (var i = 0; i < ascii.length; i++) {
		while (text.indexOf(ascii[i]) != -1) {
			text = text.replace(ascii[i], hex[i]);
		}
	}
	text = text.replace(/!$#/g, "%25");
	return text;
};

/**
 * Gets the actual mouse position relative to the document.
 *
 * @this {Util}
 * @param {Object} e The mouse event data.
 * @return {string} The transformed hex code.
 */
Util.getMousePosition = function(event) {
	if (!event) {
		event = window.event;
	}
	var body = (window.document.compatMode && window.document.compatMode == "CSS1Compat") ? window.document.documentElement : window.document.body;
	return {
		top: event.pageY ? event.pageY : event.clientY,
		left: event.pageX ? event.pageX : event.clientX
	};
};

/**
 * This method is required for lazy loading of images. It appends the image to the parent div and rescales the image if it is too large.
 *
 * @this {Util}
 * @param {Image} response The response after an image has been loaded.
 * @param {HTML} image The images'  html tag.
 * @param {HTML} div The parent DIV which contains the image.
 */
var imageLoad = function(response, image, div) {
	var width = div.width(), height = div.height();
	var w = response.naturalWidth || response.width, h = response.naturalHeight || response.height;
	if (typeof w != "undefined" && w > 0) {
		if (h > height) {
			image.attr('height', height);
			h = height;
		}
		if (w > width) {
			image.attr('width', width);
			w = width;
		}
		var top = (height - $(image).height()) / 2;
		var left = (width - $(image).width()) / 2;
		image.css('position', 'absolute');
		image.css('top', top + 'px');
		image.css('left', left + 'px');
		image.parent().css("background-image", "none");
	}
}
<commit_msg>removed debug statement<commit_after>/**
 * Provides utilities for the system. (e.g. it loads documents, facets, tags and tooltip texts)
 *
 * @constructor
 * @this {Util}
 */
var Util = new function() {
	/* array for all documents metadata from the server */
	this.documents = [];
	/* array for all possible facets */
	this.facets = [];
	/* array of strings for all possible document views */
	// this.docTypes = ['outline', 'text', 'pages', 'thumbnails', 'images', 'tei', 'map', 'tags'];
	// change this to make the gui look other wise
	this.docTypes = [
		{
			type: 'text',
			icon: 'type'
		},
		{
			type: 'outline',
			icon: 'list-nested'
		},
		{
			type: 'pages',
			icon: 'file'
		},
		{
			type: 'thumbnails',
			icon: 'grid'
		},
		{
			type: 'images',
			icon: 'image'
		},
		{
			type: 'tei',
			icon: 'tree'
		},
		{
			type: 'map',
			icon: 'earth'
		},
		{
			type: 'tags',
			icon: 'cloud'
		}
	];
	this.docsLoaded = -1;
	this.facetsLoaded = false;
	this.texts = EditionTooltips;
	if (typeof navigator.language != 'undefined' && navigator.language.indexOf("de") > -1) {
		this.language = "de";
	}
	else
		if (typeof navigator.browserLanguage != 'undefined' && navigator.browserLanguage.indexOf("de") > -1) {
			this.language = "de";
		}
		else {
			this.language = "en";
		}
};

/**
 * Getter for an ID for a given document type. Required for generating magnetic links.
 *
 * @this {Util}
 * @param {string} type The document type.
 * @return {number} The corresponding document type id.
 */
Util.getIdByType = function(type) {
	for (var i = 0; i < this.docTypes.length; i++) {
		if (this.docTypes[i].type == type) {
			return i;
		}
	}
};

/**
 * Getter for the document type of a given ID. Required for utilizing magnetic links.
 *
 * @this {Util}
 * @param {number} id The id of the document type.
 * @return {string} The corresponding document type.
 */
Util.getTypeById = function(id) {
	return this.docTypes[id];
};

/**
 * Getter for a specific attribute of a given node. Required for attribute names with name spaces like 'xml:href'.
 *
 * @this {Util}
 * @param {XML} node The XML node to get the attribute.
 * @param {string} attributeName The attribute to search for.
 * @return {string} The value of the attribute.
 */
Util.getAttribute = function(node, attributeName) {
	var value = $(node).attr(attributeName);
	if (typeof value == 'undefined') {
		for (var i = 0; i < node.attributes.length; i++) {
			if (attributeName == node.attributes[i].name) {
				value = node.attributes[i].nodeValue;
				break;
			}
		}
	}
	return value;
};

/**
 * Getter for a language dependent tooltip string.
 *
 * @this {Util}
 * @param {string} id The id of the tooltip, e.g. 'folder'.
 * @return {string} The corresponding tooltip dependent on the applications language.
 */
Util.getString = function(id) {
	var label = '';

	try {
		label = Util.texts[id][Util.language];
	} catch (e) {
		// console.log(e);
	}
	return label;
};

/**
 * Asynchronous loader for all available documents. This function retrieves the metadata file for all documents. Afterwards, each single document gets retrieved.
 *
 * @this {Util}
 * @param {Object} trigger A trigger function to be called when a document has been loaded.
 */
Util.loadDocuments = function(trigger) {
	Util.docsLoaded = 0;
	var gui = this;
	var callback = function(xml) {
		var docs = $(xml).find('doc');
		var loadMets = function(document) {
			var imagePath, images = [];
			var metsCallback = function(xml) {
				$(xml).find('[nodeName="METS:mets"]').find('[nodeName="METS:fileSec"]').find('[nodeName="METS:fileGrp"]').first().find('[nodeName="METS:file"]').each(function() {
					var node = $(this).find('[nodeName="METS:FLocat"]')[0];
					var fullPath = Util.getAttribute(node, 'xlink:href');
					images.push(fullPath.substring(fullPath.lastIndexOf("/") + 1));
					if (!imagePath) {
						var dummy = fullPath.substring(0, fullPath.lastIndexOf("/"));
						imagePath = dummy.substring(0, dummy.lastIndexOf("/") + 1);
					}
				})
				document.imagePath = imagePath;
				document.images = images;
				Util.documents.push(document);
				if (Util.documents.length == docs.length) {
					Util.docsLoaded = 1;
				}
				if (typeof trigger != 'undefined') {
					trigger(document);
				}
			};
			DocumentServerConnection.getMets(document.title, true, metsCallback);
		}
		for (var i = 0; i < docs.length; i++) {

			var fullText = function() {
				return $(docs[i]).find('fulltext');
			}

			if (fullText()) {
				var document = new Document(
						$(docs[i]).find('id').text(),
						$(docs[i]).find('title').text(),
						$(docs[i]).find('titleShort').text(),
						$(docs[i]).find('preview').text(),
						$(docs[i]).find('pageCount').text(),
						fullText()
				);
				loadMets(document);
			}
		}
	}
	DocumentServerConnection.getDocuments(callback);
};

/**
 * Loads a single document synchronously. Used for utilizing magnetic links; the intention is to load related documents before unrelated ones.
 *
 * @this {Util}
 * @param {string} title The title (id) of the document.
 * @param {string} nameShort The short name of the document.
 */
Util.loadDocumentSync = function(title, nameShort) {
	var pageCount;

	var document = new Document(title, '', nameShort, '');
	var imagePath, images = [];
	var metsCallback = function(xml) {
		$(xml).find('[nodeName="METS:mets"]').find('[nodeName="METS:fileSec"]').find('[nodeName="METS:fileGrp"]').first().find('[nodeName="METS:file"]').each(function() {
			var node = $(this).find('[nodeName="METS:FLocat"]')[0];
			var fullPath = Util.getAttribute(node, 'xlink:href');
			images.push(fullPath.substring(fullPath.lastIndexOf("/") + 1));
			if (!imagePath) {
				var dummy = fullPath.substring(0, fullPath.lastIndexOf("/"));
				imagePath = dummy.substring(0, dummy.lastIndexOf("/") + 1);
			}
		})
		pageCount = $(xml).find('body').find('div').last().find('pb').last().attr('n')
		document.pages = pageCount;
		document.imagePath = imagePath;
		document.images = images;
	};
	DocumentServerConnection.getMets(document.title, false, metsCallback);
	return document;
};

/**
 * Getter for a document with a specific title. Currently used from the external Scrips page.
 *
 * @this {Util}
 * @param {string} title The title (id) of the document.
 * @return {Document} The document with the given title.
 */
Util.getDoc = function(title) {
	for (var i in Util.documents) {
		if (Util.documents[i].title == title) {
			return Util.documents[i];
		}
	}
};

/**
 * Loader for the possible facets of a document. Finally, we receive 2 types of facets:
 * 1) facets for rendering in texts, e.g. 'tei:placeName' for place names
 * 2) facets for additional information for documents, e.g. 'tei:note' for notes for a document
 *
 * @this {Util}
 * @param {Object} trigger A trigger function to be called when the facets have been loaded.
 */
Util.loadFacets = function(trigger) {
	var i = 0;
	var callback = function(xml) {
		$(xml).find('[nodeName="tei:tei"]').children().each(function() {
			var labels = [];
			$(this).find('foreign').each(function() {
				labels.push({
								language: Util.getAttribute(this, 'xml:lang'),
								label: $(this).text()
							});
			});
			var render = $(this).attr("rend");
			var color = undefined;
			if (render) {
				var dummy = $("
").appendTo('body');
				color = $(dummy).css('color');
				$(dummy).remove();
				i++;
			}
			var facet = {
				facet: this.nodeName,
				render: render,
				labels: labels,
				color: color
			};
			Util.facets.push(facet);
			if (typeof trigger != 'undefined' && render) {
				trigger(facet);
			}
		});
		Util.facetsLoaded = true;
	}
	DocumentServerConnection.getFacets(callback);
};

/**
 * Getter for a label of a facet in the proper language.
 *
 * @this {Util}
 * @param {JSON} facet The facet to get the label.
 * @return {string} The label of the facet in the browsers language.
 */
Util.getFacetLabel = function(facet) {
	for (var i in facet.labels) {
		if (facet.labels[i].language == Util.language) {
			return facet.labels[i].label;
		}
	}
};

/**
 * Getter for a facet by a given id.
 *
 * @this {Util}
 * @param {string} The id of the facet, e.g. 'tei:placeName'.
 * @return {JSON} The JSON data of the corresponding facet.
 */
Util.getFacet = function(id) {
	for (var i in Util.facets) {
		if (Util.facets[i].facet == id) {
			return Util.facets[i];
		}
	}
};

/**
 * Getter for a specific number (EditionProperties.maxTags) of tags from the given xml file.
 *
 * @this {Util}
 * @param {XML} tags The xml file containing the tag data.
 * @return {Array} A specific number of most frequent tags.
 */
Util.getTags = function(tags) {
	var tagArray = [];
	for (var i = 0; i < tags.length; i++) {
		var text = $(tags[i]).find('tag').text();
		if (text == "") {
			continue;
		}
		var weight = $(tags[i]).find('count').text();
		var url = $(tags[i]).find('link').text();
		var facet = Util.getFacet($(tags[i]).find('facet').text());
		var tooltip = weight + " " + Util.getString('occurences');
		var link = '' + text + '';
		tagArray.push({
						  text: link,
						  weight: weight
					  });
		if (EditionProperties.maxTags && EditionProperties.maxTags == tagArray.length) {
			break;
		}
	}
	return tagArray;
};

/**
 * Returns the HTML for an error message of a given type; used to communicate AJAX response errors when loading data from the server.
 *
 * @this {Util}
 * @param {number} errorType The error id (e.g. 404).
 * @return {HTML} The error message content DIV.
 */
Util.getErrorMessage = function(errorType) {
	var errorDiv = $("
");
	$("
" + Util.getString('errorMessage') + ' (' + errorType + ')' + "
").appendTo(errorDiv);
	return errorDiv;
};

/**
 * Returns the HTML for an alert message. Used to communicate if the user needs to select facets when required.
 *
 * @this {Util}
 * @param {string} message The alert message to be shown.
 * @return {HTML} The alert message content DIV.
 */
Util.getAlertMessage = function(message) {
	var alertDiv = $("
");
	$("
" + message + "
").appendTo(alertDiv);
	return alertDiv;
};

/**
 * Used to transform Ascii to HEX code for generating working hyperlinks.
 *
 * @this {Util}
 * @param {string} text The text to transform.
 * @return {string} The transformed hex code.
 */
Util.asciiToHex = function(text) {
	var ascii = new Array("!", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "/", ":", ";", "=", "?", "@", "[", "]", " ");
	var hex = new Array("%21", "%23", "%24", "!$#", "%26", "%27", "%28", "%29", "%2a", "%2b", "%2c", "%2f", "%3a", "%3b", "%3d", "%3f", "%40", "%5b", "%5d", "%20");
	for (var i = 0; i < ascii.length; i++) {
		while (text.indexOf(ascii[i]) != -1) {
			text = text.replace(ascii[i], hex[i]);
		}
	}
	text = text.replace(/!$#/g, "%25");
	return text;
};

/**
 * Gets the actual mouse position relative to the document.
 *
 * @this {Util}
 * @param {Object} e The mouse event data.
 * @return {string} The transformed hex code.
 */
Util.getMousePosition = function(event) {
	if (!event) {
		event = window.event;
	}
	var body = (window.document.compatMode && window.document.compatMode == "CSS1Compat") ? window.document.documentElement : window.document.body;
	return {
		top: event.pageY ? event.pageY : event.clientY,
		left: event.pageX ? event.pageX : event.clientX
	};
};

/**
 * This method is required for lazy loading of images. It appends the image to the parent div and rescales the image if it is too large.
 *
 * @this {Util}
 * @param {Image} response The response after an image has been loaded.
 * @param {HTML} image The images'  html tag.
 * @param {HTML} div The parent DIV which contains the image.
 */
var imageLoad = function(response, image, div) {
	var width = div.width(), height = div.height();
	var w = response.naturalWidth || response.width, h = response.naturalHeight || response.height;
	if (typeof w != "undefined" && w > 0) {
		if (h > height) {
			image.attr('height', height);
			h = height;
		}
		if (w > width) {
			image.attr('width', width);
			w = width;
		}
		var top = (height - $(image).height()) / 2;
		var left = (width - $(image).width()) / 2;
		image.css('position', 'absolute');
		image.css('top', top + 'px');
		image.css('left', left + 'px');
		image.parent().css("background-image", "none");
	}
}
<|endoftext|>


<commit_before>const { setWorldConstructor, setDefaultTimeout, Given, When, Then } = require('cucumber')
const path = require('path')
const os = require('os')
const childProcess = require('child_process')
const { promisify } = require('util')
const isTravis = require('is-travis')
const Application = require('spectron').Application
const $ = require('cheerio')
const jetpack = require('fs-jetpack')
const { git, clone } = require('../../app/lib/git')
const exec = promisify(childProcess.exec)

const appPath = path.join(__dirname, '../../app')
const homeDir = path.join(__dirname, '../../test/fixtures/home')
const pluginDir = path.join(homeDir, '.zazu/plugins')
const calcProfile = path.join(homeDir, '.calculator.zazurc.json')
const fallbackProfile = path.join(homeDir, '.fallback.zazurc.json')
const homeProfile = path.join(homeDir, '.zazurc.json')

setDefaultTimeout(60 * 1000)

class World {
  profile (name) {
    this.profileType = name
    if (this.profileType === 'calculator') {
      jetpack.copy(calcProfile, homeProfile, { overwrite: true })
    } else if (this.profileType === 'fallback') {
      jetpack.copy(fallbackProfile, homeProfile, { overwrite: true })
    }
    this.app = new Application({
      path: require('electron'),
      args: [appPath],
      env: {
        NODE_ENV: 'test',
        ZAZU_HOME: homeDir,
      },
    })
    return Promise.resolve()
  }

  open () {
    return this.app.start().then(() => {
      const time = this.profileType === 'calculator' ? 50 * 1000 : 5 * 1000
      return wait(time) // give it time to load plugins
    })
  }

  isWindowVisible () {
    return this.app.browserWindow.isVisible()
  }

  hasResults () {
    return this.app.client.isExisting('.results')
  }

  accessibility () {
    return this.app.client.auditAccessibility()
  }

  updatePlugins () {
    return this.app.webContents.send('updatePlugins').then(() => {
      return wait(10 * 1000) // give it time to update the plugin
    })
  }

  getQuery () {
    return this.app.client.getValue('input')
  }

  async type (input) {
    for (const char of input) {
      await this.hitKey(char)
    }
    await wait(500)
  }

  showWindow () {
    return this.hitKey('space', 'shift')
  }

  hideWindow () {
    return this.hitKey('space', 'shift')
  }

  hitKey (key, modifier) {
    if (os.type() === 'Darwin' && !isTravis) {
      const keyForAppleScript = key.length === 1 ? `\\"${key}\\"` : key
      if (modifier) {
        const modifierForAppleScript = modifier.replace('alt', 'option')
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript} using ${modifierForAppleScript} down"
          osascript -e "$Script"`)
      } else {
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript}"
          osascript -e "$Script"`)
      }
    }
    const robot = require('robotjs')
    if (modifier) {
      return Promise.resolve(robot.keyTap(key, modifier))
    } else {
      // * will becomes 8, see https://github.com/octalmage/robotjs/issues/285
      const robotjsBadChars = /[~!@#$%^&*()_+{}|:"<>?]/
      let match = key.match(robotjsBadChars)
      if (match) {
        return Promise.resolve(robot.keyTap(key, 'shift'))
      }
      return Promise.resolve(robot.keyTap(key))
    }
  }

  close () {
    if (this.app) {
      return this.app.stop()
    }
    return Promise.resolve()
  }

  clickActiveResult () {
    return this.app.client.click('li.active')
  }

  getActiveHeader () {
    return this.app.client.getText('li.active h2')
  }

  getResults () {
    return this.app.client.getText('.results')
  }

  getResultItems () {
    return this.app.client.getHTML('.results').then(results => {
      return $(results).find('li')
    })
  }

  readClipboard () {
    return this.app.electron.clipboard.readText()
  }
}

const wait = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

const eventually = (func, expectedValue) => {
  const assert = actualValue => {
    if (actualValue !== expectedValue) {
      throw new Error('Values didnt match')
    }
  }
  return new Promise((resolve, reject) => {
    let iterations = 0
    const retry = () => {
      iterations++
      if (iterations >= 50) {
        reject(new Error('Forever is a long time'))
      } else {
        func()
          .then(assert)
          .then(resolve)
          .catch(() => {
            return wait(100).then(retry)
          })
      }
    }
    retry()
  })
}

setWorldConstructor(World)

Given('I have {string} as a plugin', function (plugin) {
  if (plugin === 'tinytacoteam/zazu-fixture') {
    return this.profile('default')
  } else if (plugin === 'tinytacoteam/zazu-calculator') {
    return this.profile('calculator')
  }
  return Promise.reject(new Error('Profile not found'))
})

Given('the app is launched', { timeout: 120 * 1000 }, function () {
  return this.open()
})

Given('I have {string} installed before packagist support', function (plugin) {
  const fallbackDir = path.join(pluginDir, plugin)
  return clone(plugin, fallbackDir)
    .then(() => {
      return git(['reset', '--hard', '16e4e50'], { cwd: fallbackDir })
    })
    .then(() => {
      return this.profile('fallback')
    })
})

Given('I update the plugins', { timeout: 15 * 1000 }, function () {
  return this.updatePlugins()
})

When('I toggle it open', function () {
  return this.showWindow()
})

When('I toggle it closed', function () {
  return this.hideWindow()
})

// assumes modifier is first
When('I hit the hotkey {string}', function (hotkey) {
  var keys = hotkey.split('+')
  return this.hitKey(keys[1], keys[0]).then(() => {
    return wait(100)
  })
})

When('I hit the key {string}', function (hotkey) {
  return this.hitKey(hotkey).then(() => {
    return wait(100)
  })
})

When('I eventually click on the active result', function () {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return this.clickActiveResult()
    })
})

Then('my clipboard contains {string}', function (expected, callback) {
  this.readClipboard().then(actual => {
    if (actual === expected) {
      callback()
    } else {
      callback(new Error('Expected "' + expected + '" to be in your clipbaord but found "' + actual + '"'))
    }
  })
})

Then('the search window is not visible', function () {
  return eventually(() => this.isWindowVisible(), false)
})

Then('the search window is visible', function () {
  return eventually(() => this.isWindowVisible(), true)
})

Then('I have {int} result(s)', function (expected) {
  return eventually(() => {
    return this.getResultItems().then(items => items.length)
  }, parseInt(expected, 10))
})

Then('the input is empty', function () {
  return eventually(() => {
    return this.getQuery()
  }, '')
})

Then('the input is {string}', function (expected) {
  return eventually(() => {
    return this.getQuery()
  }, expected)
})

When('I type in {string}', function (input) {
  return this.type(input).then(() =>
    eventually(() => {
      return this.getQuery()
    }, input)
  )
})

Then('I have no results', function () {
  return eventually(() => this.hasResults(), false)
})

Then('the active result contains {string}', function (header) {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return eventually(() => this.getActiveHeader(), header)
    })
})

Then('I have no accessibility warnings', function () {
  return this.accessibility().then(response => {
    if (response.results.length !== 0) {
      throw new Error('You have accessibility issues')
    }
  })
})

Then('the results contain {string}', function (subset, callback) {
  this.getResults().then(resultText => {
    if (resultText.match(subset)) {
      callback()
    } else {
      callback(new Error('Expected results to contain "' + subset + '"'))
    }
  })
})
<commit_msg>fix: cucumber execution path<commit_after>const { setWorldConstructor, setDefaultTimeout, Given, When, Then } = require('cucumber')
const path = require('path')
const os = require('os')
const childProcess = require('child_process')
const { promisify } = require('util')
const isTravis = require('is-travis')
const Application = require('spectron').Application
const $ = require('cheerio')
const jetpack = require('fs-jetpack')
const { git, clone } = require('../../app/lib/git')
const exec = promisify(childProcess.exec)

const appPath = path.join(__dirname, '../../')
const homeDir = path.join(__dirname, '../../test/fixtures/home')
const pluginDir = path.join(homeDir, '.zazu/plugins')
const calcProfile = path.join(homeDir, '.calculator.zazurc.json')
const fallbackProfile = path.join(homeDir, '.fallback.zazurc.json')
const homeProfile = path.join(homeDir, '.zazurc.json')

setDefaultTimeout(60 * 1000)

class World {
  profile (name) {
    this.profileType = name
    if (this.profileType === 'calculator') {
      jetpack.copy(calcProfile, homeProfile, { overwrite: true })
    } else if (this.profileType === 'fallback') {
      jetpack.copy(fallbackProfile, homeProfile, { overwrite: true })
    }
    this.app = new Application({
      path: require('electron'),
      args: [appPath],
      env: {
        NODE_ENV: 'test',
        ZAZU_HOME: homeDir,
      },
    })
    return Promise.resolve()
  }

  open () {
    return this.app.start().then(() => {
      const time = this.profileType === 'calculator' ? 50 * 1000 : 5 * 1000
      return wait(time) // give it time to load plugins
    })
  }

  isWindowVisible () {
    return this.app.browserWindow.isVisible()
  }

  hasResults () {
    return this.app.client.isExisting('.results')
  }

  accessibility () {
    return this.app.client.auditAccessibility()
  }

  updatePlugins () {
    return this.app.webContents.send('updatePlugins').then(() => {
      return wait(10 * 1000) // give it time to update the plugin
    })
  }

  getQuery () {
    return this.app.client.getValue('input')
  }

  async type (input) {
    for (const char of input) {
      await this.hitKey(char)
    }
    await wait(500)
  }

  showWindow () {
    return this.hitKey('space', 'shift')
  }

  hideWindow () {
    return this.hitKey('space', 'shift')
  }

  hitKey (key, modifier) {
    if (os.type() === 'Darwin' && !isTravis) {
      const keyForAppleScript = key.length === 1 ? `\\"${key}\\"` : key
      if (modifier) {
        const modifierForAppleScript = modifier.replace('alt', 'option')
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript} using ${modifierForAppleScript} down"
          osascript -e "$Script"`)
      } else {
        return exec(`Script="tell app \\"System Events\\" to keystroke ${keyForAppleScript}"
          osascript -e "$Script"`)
      }
    }
    const robot = require('robotjs')
    if (modifier) {
      return Promise.resolve(robot.keyTap(key, modifier))
    } else {
      // * will becomes 8, see https://github.com/octalmage/robotjs/issues/285
      const robotjsBadChars = /[~!@#$%^&*()_+{}|:"<>?]/
      let match = key.match(robotjsBadChars)
      if (match) {
        return Promise.resolve(robot.keyTap(key, 'shift'))
      }
      return Promise.resolve(robot.keyTap(key))
    }
  }

  close () {
    if (this.app) {
      return this.app.stop()
    }
    return Promise.resolve()
  }

  clickActiveResult () {
    return this.app.client.click('li.active')
  }

  getActiveHeader () {
    return this.app.client.getText('li.active h2')
  }

  getResults () {
    return this.app.client.getText('.results')
  }

  getResultItems () {
    return this.app.client.getHTML('.results').then(results => {
      return $(results).find('li')
    })
  }

  readClipboard () {
    return this.app.electron.clipboard.readText()
  }
}

const wait = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

const eventually = (func, expectedValue) => {
  const assert = actualValue => {
    if (actualValue !== expectedValue) {
      throw new Error('Values didnt match')
    }
  }
  return new Promise((resolve, reject) => {
    let iterations = 0
    const retry = () => {
      iterations++
      if (iterations >= 50) {
        reject(new Error('Forever is a long time'))
      } else {
        func()
          .then(assert)
          .then(resolve)
          .catch(() => {
            return wait(100).then(retry)
          })
      }
    }
    retry()
  })
}

setWorldConstructor(World)

Given('I have {string} as a plugin', function (plugin) {
  if (plugin === 'tinytacoteam/zazu-fixture') {
    return this.profile('default')
  } else if (plugin === 'tinytacoteam/zazu-calculator') {
    return this.profile('calculator')
  }
  return Promise.reject(new Error('Profile not found'))
})

Given('the app is launched', { timeout: 120 * 1000 }, function () {
  return this.open()
})

Given('I have {string} installed before packagist support', function (plugin) {
  const fallbackDir = path.join(pluginDir, plugin)
  return clone(plugin, fallbackDir)
    .then(() => {
      return git(['reset', '--hard', '16e4e50'], { cwd: fallbackDir })
    })
    .then(() => {
      return this.profile('fallback')
    })
})

Given('I update the plugins', { timeout: 15 * 1000 }, function () {
  return this.updatePlugins()
})

When('I toggle it open', function () {
  return this.showWindow()
})

When('I toggle it closed', function () {
  return this.hideWindow()
})

// assumes modifier is first
When('I hit the hotkey {string}', function (hotkey) {
  var keys = hotkey.split('+')
  return this.hitKey(keys[1], keys[0]).then(() => {
    return wait(100)
  })
})

When('I hit the key {string}', function (hotkey) {
  return this.hitKey(hotkey).then(() => {
    return wait(100)
  })
})

When('I eventually click on the active result', function () {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return this.clickActiveResult()
    })
})

Then('my clipboard contains {string}', function (expected, callback) {
  this.readClipboard().then(actual => {
    if (actual === expected) {
      callback()
    } else {
      callback(new Error('Expected "' + expected + '" to be in your clipbaord but found "' + actual + '"'))
    }
  })
})

Then('the search window is not visible', function () {
  return eventually(() => this.isWindowVisible(), false)
})

Then('the search window is visible', function () {
  return eventually(() => this.isWindowVisible(), true)
})

Then('I have {int} result(s)', function (expected) {
  return eventually(() => {
    return this.getResultItems().then(items => items.length)
  }, parseInt(expected, 10))
})

Then('the input is empty', function () {
  return eventually(() => {
    return this.getQuery()
  }, '')
})

Then('the input is {string}', function (expected) {
  return eventually(() => {
    return this.getQuery()
  }, expected)
})

When('I type in {string}', function (input) {
  return this.type(input).then(() =>
    eventually(() => {
      return this.getQuery()
    }, input)
  )
})

Then('I have no results', function () {
  return eventually(() => this.hasResults(), false)
})

Then('the active result contains {string}', function (header) {
  return eventually(() => this.hasResults(), true)
    .then(() => {
      return wait(100)
    })
    .then(() => {
      return eventually(() => this.getActiveHeader(), header)
    })
})

Then('I have no accessibility warnings', function () {
  return this.accessibility().then(response => {
    if (response.results.length !== 0) {
      throw new Error('You have accessibility issues')
    }
  })
})

Then('the results contain {string}', function (subset, callback) {
  this.getResults().then(resultText => {
    if (resultText.match(subset)) {
      callback()
    } else {
      callback(new Error('Expected results to contain "' + subset + '"'))
    }
  })
})
<|endoftext|>


<commit_before>import moment from 'moment';

export const STORY_PUB_DATE_UNDATEABLE = 'undateable';

const SOLR_DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';

const SHORT_SOLR_DATE_FORMAT = 'YYYY-MM-DD';

const GAP_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ssZ';

const DB_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';

const JOB_STATUS_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';

const TOPIC_DATE_FORMAT = 'YYYY-MM-DD';

// "2017-04-13 12:26:59.649513"
const SNAPSHOT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSSSS';

export const ONE_DAY_RANGE = '+1DAY';

export const PAST_WEEK = 'week';
export const PAST_TWO_WEEKS = 'two_weeks';
export const PAST_MONTH = 'month';
export const PAST_YEAR = 'year';
export const PAST_ALL = 'all-time';

export function getCurrentDate() {
  const testdate = moment().format(TOPIC_DATE_FORMAT);
  return testdate;
}

export function getShortDate(dateString) {
  return moment(dateString).format('ll');
}

export function getDateFromTimestamp(milliseconds) {
  return moment.utc(parseInt(milliseconds, 10)).format(GAP_DATE_FORMAT);
}

export function isStartDateAfterEndDate(start, end) {
  return moment(start).isAfter(end);
}

export function oneWeekLater(date) {
  const weekLater = moment(date).add(1, 'week');
  return weekLater;
}

export function oneMonthBefore(date) {
  const monthBefore = moment(date).subtract(1, 'month').format(TOPIC_DATE_FORMAT);
  return monthBefore;
}

export function isMoreThanAYearInPast(dateInPast) {
  const yearPriorToNow = moment().subtract(1, 'years');
  return yearPriorToNow.isAfter(dateInPast);
}

export function isADayDifference(date0, date1) {
  const firstDate = moment(date0);
  const oneDayAfter = firstDate.add(1, 'days');
  return oneDayAfter.isAfter(moment(date1));
}

export function solrFormat(date, short = true) {
  return moment(date).format(short ? SHORT_SOLR_DATE_FORMAT : SOLR_DATE_FORMAT);
}

export function getMomentDateSubtraction(date, num, timeUnit) {
  return moment(date).subtract(num, timeUnit).format(TOPIC_DATE_FORMAT);
}

export function topicDateToMoment(topicDate, strict = true) {
  return moment(topicDate, TOPIC_DATE_FORMAT, strict);
}

export function snapshotDateToMoment(snapshotDateStr, strict = false) {
  return moment(snapshotDateStr, SNAPSHOT_DATE_FORMAT, strict);
}

export function jobStatusDateToMoment(statusDate, strict = true) {
  return moment(statusDate, JOB_STATUS_DATE_FORMAT, strict);
}

export function healthStartDateToMoment(healthStartDate, strict = true) {
  return moment(healthStartDate.substring(0, DB_DATE_FORMAT.length), DB_DATE_FORMAT, strict);
}

export function sourceSuggestionDateToMoment(suggestionDate, strict = true) {
  return moment(suggestionDate.substring(0, DB_DATE_FORMAT.length), DB_DATE_FORMAT, strict);
}

function solrDateToMoment(solrDateString, strict = true) {
  return moment(solrDateString, SOLR_DATE_FORMAT, strict);
}

export function storyPubDateToMoment(solrDateString, strict = true) {
  return moment(solrDateString, DB_DATE_FORMAT, strict);
}

export function storyPubDateToTimestamp(solrDateString, strict = true) {
  return storyPubDateToMoment(solrDateString, strict).valueOf();
}

function gapDateToMomemt(gapDateString, strict = true) {
  return moment(gapDateString, GAP_DATE_FORMAT, strict);
}

// Helper to change solr dates (2015-12-14T00:00:00Z) into javascript date ojects
export function calcSentences(countsMap) {
  let sentenceCount = 0;
  Object.keys(countsMap).forEach((k) => {
    if (k !== 'end' && k !== 'gap' && k !== 'start') {
      const v = countsMap[k];
      sentenceCount += v;
    }
  });
  return sentenceCount;
}
// Helper to change solr dates (2015-12-14T00:00:00Z) into javascript date ojects
export function cleanDateCounts(countsMap) {
  const countsArray = [];
  Object.keys(countsMap).forEach((k) => {
    if (k !== 'end' && k !== 'gap' && k !== 'start') {
      const v = countsMap[k];
      const timestamp = solrDateToMoment(k).valueOf();
      countsArray.push({ date: timestamp, count: v });
    }
  });
  return countsArray.sort((a, b) => a.date - b.date);
}

// Turn a gap list into a list of objects with from/to/color attributes, suitable for use as plot bands in HighCharts
export function cleanCoverageGaps(gapList) {
  let plotBands = [];
  if (gapList) {  // if the source has no health data, gapList could be undefined
    plotBands = gapList.map((gap) => {
      const weekStart = gapDateToMomemt(gap.stat_week).valueOf();
      const weekEnd = weekStart + (604800 * 1000);    // + one week
      return {
        from: weekStart,
        to: weekEnd,
        color: 'rgba(255, 0, 0, .6)',
      };
    });
  }
  return plotBands;
}

export function prepDateForSolrQuery(startDate, endDate) {
  return `(publish_date:[${topicDateToMoment(startDate).format('YYYY-MM-DDThh:mm:ss')}Z TO ${topicDateToMoment(endDate).format('YYYY-MM-DDThh:mm:ss')}Z])`;
}

export function getDateRange(timePeriod) {
  if (![PAST_WEEK, PAST_TWO_WEEKS, PAST_MONTH, PAST_YEAR, PAST_ALL].includes(timePeriod)) {
    const error = { message: `unknown time period passed to calculateTimePeriods: ${timePeriod}` };
    throw error;
  }
  let targetPeriodStart = null;
  const targetYear = moment().subtract(1, 'year');
  const targetMonth = moment().subtract(1, 'month');
  const targetWeek = moment().subtract(1, 'week');
  const targetPastTwoWeeks = moment().subtract(2, 'week');
  switch (timePeriod) {
    case PAST_WEEK: // past week
      targetPeriodStart = targetWeek;
      break;
    case PAST_TWO_WEEKS: // past week
      targetPeriodStart = targetPastTwoWeeks;
      break;
    case PAST_MONTH: // past month
      targetPeriodStart = targetMonth;
      break;
    case PAST_YEAR:
      targetPeriodStart = targetYear;
      break;
    case PAST_ALL:
      return null;
    default:
      break;
  }
  return { start: targetPeriodStart, end: moment() };
}

export function getPastTwoWeeksDateRange() {
  const dateObj = getDateRange(PAST_TWO_WEEKS);
  dateObj.start = dateObj.start.format('YYYY-MM-DD');
  dateObj.end = dateObj.end.format('YYYY-MM-DD');
  return dateObj;
}

export function calculateTimePeriods(timePeriod) {
  const dates = getDateRange(timePeriod);
  if (dates === null) { // ie. PAST_ALL
    return '';
  }
  return `(publish_date:[${dates.start.format('YYYY-MM-DDThh:mm:ss')}Z TO ${dates.end.format('YYYY-MM-DDThh:mm:ss')}Z])`;
}
<commit_msg>cache results from periodic word cloud (#1003)<commit_after>import moment from 'moment';

export const STORY_PUB_DATE_UNDATEABLE = 'undateable';

const SOLR_DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';

const SHORT_SOLR_DATE_FORMAT = 'YYYY-MM-DD';

const GAP_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ssZ';

const DB_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';

const JOB_STATUS_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';

const TOPIC_DATE_FORMAT = 'YYYY-MM-DD';

// "2017-04-13 12:26:59.649513"
const SNAPSHOT_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSSSSS';

export const ONE_DAY_RANGE = '+1DAY';

export const PAST_WEEK = 'week';
export const PAST_TWO_WEEKS = 'two_weeks';
export const PAST_MONTH = 'month';
export const PAST_YEAR = 'year';
export const PAST_ALL = 'all-time';

export function getCurrentDate() {
  const testdate = moment().format(TOPIC_DATE_FORMAT);
  return testdate;
}

export function getShortDate(dateString) {
  return moment(dateString).format('ll');
}

export function getDateFromTimestamp(milliseconds) {
  return moment.utc(parseInt(milliseconds, 10)).format(GAP_DATE_FORMAT);
}

export function isStartDateAfterEndDate(start, end) {
  return moment(start).isAfter(end);
}

export function oneWeekLater(date) {
  const weekLater = moment(date).add(1, 'week');
  return weekLater;
}

export function oneMonthBefore(date) {
  const monthBefore = moment(date).subtract(1, 'month').format(TOPIC_DATE_FORMAT);
  return monthBefore;
}

export function isMoreThanAYearInPast(dateInPast) {
  const yearPriorToNow = moment().subtract(1, 'years');
  return yearPriorToNow.isAfter(dateInPast);
}

export function isADayDifference(date0, date1) {
  const firstDate = moment(date0);
  const oneDayAfter = firstDate.add(1, 'days');
  return oneDayAfter.isAfter(moment(date1));
}

export function solrFormat(date, short = true) {
  return moment(date).format(short ? SHORT_SOLR_DATE_FORMAT : SOLR_DATE_FORMAT);
}

export function getMomentDateSubtraction(date, num, timeUnit) {
  return moment(date).subtract(num, timeUnit).format(TOPIC_DATE_FORMAT);
}

export function topicDateToMoment(topicDate, strict = true) {
  return moment(topicDate, TOPIC_DATE_FORMAT, strict);
}

export function snapshotDateToMoment(snapshotDateStr, strict = false) {
  return moment(snapshotDateStr, SNAPSHOT_DATE_FORMAT, strict);
}

export function jobStatusDateToMoment(statusDate, strict = true) {
  return moment(statusDate, JOB_STATUS_DATE_FORMAT, strict);
}

export function healthStartDateToMoment(healthStartDate, strict = true) {
  return moment(healthStartDate.substring(0, DB_DATE_FORMAT.length), DB_DATE_FORMAT, strict);
}

export function sourceSuggestionDateToMoment(suggestionDate, strict = true) {
  return moment(suggestionDate.substring(0, DB_DATE_FORMAT.length), DB_DATE_FORMAT, strict);
}

function solrDateToMoment(solrDateString, strict = true) {
  return moment(solrDateString, SOLR_DATE_FORMAT, strict);
}

export function storyPubDateToMoment(solrDateString, strict = true) {
  return moment(solrDateString, DB_DATE_FORMAT, strict);
}

export function storyPubDateToTimestamp(solrDateString, strict = true) {
  return storyPubDateToMoment(solrDateString, strict).valueOf();
}

function gapDateToMomemt(gapDateString, strict = true) {
  return moment(gapDateString, GAP_DATE_FORMAT, strict);
}

// Helper to change solr dates (2015-12-14T00:00:00Z) into javascript date ojects
export function calcSentences(countsMap) {
  let sentenceCount = 0;
  Object.keys(countsMap).forEach((k) => {
    if (k !== 'end' && k !== 'gap' && k !== 'start') {
      const v = countsMap[k];
      sentenceCount += v;
    }
  });
  return sentenceCount;
}
// Helper to change solr dates (2015-12-14T00:00:00Z) into javascript date ojects
export function cleanDateCounts(countsMap) {
  const countsArray = [];
  Object.keys(countsMap).forEach((k) => {
    if (k !== 'end' && k !== 'gap' && k !== 'start') {
      const v = countsMap[k];
      const timestamp = solrDateToMoment(k).valueOf();
      countsArray.push({ date: timestamp, count: v });
    }
  });
  return countsArray.sort((a, b) => a.date - b.date);
}

// Turn a gap list into a list of objects with from/to/color attributes, suitable for use as plot bands in HighCharts
export function cleanCoverageGaps(gapList) {
  let plotBands = [];
  if (gapList) {  // if the source has no health data, gapList could be undefined
    plotBands = gapList.map((gap) => {
      const weekStart = gapDateToMomemt(gap.stat_week).valueOf();
      const weekEnd = weekStart + (604800 * 1000);    // + one week
      return {
        from: weekStart,
        to: weekEnd,
        color: 'rgba(255, 0, 0, .6)',
      };
    });
  }
  return plotBands;
}

export function prepDateForSolrQuery(startDate, endDate) {
  return `(publish_date:[${topicDateToMoment(startDate).format('YYYY-MM-DDThh:mm:ss')}Z TO ${topicDateToMoment(endDate).format('YYYY-MM-DDThh:mm:ss')}Z])`;
}

export function getDateRange(timePeriod) {
  if (![PAST_WEEK, PAST_TWO_WEEKS, PAST_MONTH, PAST_YEAR, PAST_ALL].includes(timePeriod)) {
    const error = { message: `unknown time period passed to calculateTimePeriods: ${timePeriod}` };
    throw error;
  }
  let targetPeriodStart = null;
  const today = moment()
    .hours(0)
    .minutes(0)
    .seconds(0)
    .milliseconds(0);  // make it a day so caching works better
  const targetYear = today.subtract(1, 'year');
  const targetMonth = today.subtract(1, 'month');
  const targetWeek = today.subtract(1, 'week');
  const targetPastTwoWeeks = today.subtract(2, 'week');
  switch (timePeriod) {
    case PAST_WEEK: // past week
      targetPeriodStart = targetWeek;
      break;
    case PAST_TWO_WEEKS: // past week
      targetPeriodStart = targetPastTwoWeeks;
      break;
    case PAST_MONTH: // past month
      targetPeriodStart = targetMonth;
      break;
    case PAST_YEAR:
      targetPeriodStart = targetYear;
      break;
    case PAST_ALL:
      return null;
    default:
      break;
  }
  return { start: targetPeriodStart, end: today };
}

export function getPastTwoWeeksDateRange() {
  const dateObj = getDateRange(PAST_TWO_WEEKS);
  dateObj.start = dateObj.start.format('YYYY-MM-DD');
  dateObj.end = dateObj.end.format('YYYY-MM-DD');
  return dateObj;
}

export function calculateTimePeriods(timePeriod) {
  const dates = getDateRange(timePeriod);
  if (dates === null) { // ie. PAST_ALL
    return '';
  }
  return `(publish_date:[${dates.start.format('YYYY-MM-DDThh:mm:ss')}Z TO ${dates.end.format('YYYY-MM-DDThh:mm:ss')}Z])`;
}
<|endoftext|>