<commit_before>'use strict';
var isPlainObject = require('lodash/isPlainObject');
var isFunction = require('lodash/isFunction');
var _find = require('lodash/find');
var _extend = require('lodash/extend');
var _transform = require('lodash/transform');
var Inflector = require('./inflector');

module.exports = function (jsonapi, data, opts) {
  var alreadyIncluded = [];

  function isComplexType(obj) {
    return Array.isArray(obj) || isPlainObject(obj);
  }

  function getValueForRelationship(relationshipData, included) {
    if (opts && relationshipData && opts[relationshipData.type]) {
      var valueForRelationshipFct = opts[relationshipData.type]
        .valueForRelationship;

      return valueForRelationshipFct(relationshipData, included);
    } else {
      return included;
    }
  }

  function findIncluded(relationshipData, ancestry) {
    return new Promise(function (resolve) {
      if (!jsonapi.included || !relationshipData) { resolve(null); }

      var included = _find(jsonapi.included, {
        id: relationshipData.id,
        type: relationshipData.type
      });

      if (included) {
        // To prevent circular references, check if the record type
        // has already been processed in this thread
        if (ancestry.indexOf(included.type) > -1) {
          return Promise
            .all([extractAttributes(included)])
            .then(function (results) {
              var attributes = results[0];
              var relationships = results[1];
              resolve(_extend(attributes, relationships));
            });
        }

        return Promise
          .all([extractAttributes(included), extractRelationships(included, `${ancestry}:${included.type}${included.id}`)])
          .then(function (results) {
            var attributes = results[0];
            var relationships = results[1];
            resolve(_extend(attributes, relationships));
          });
      } else {
        return resolve(null);
      }
    });
  }

  function keyForAttribute(attribute) {
    if (isPlainObject(attribute)) {
      return _transform(attribute, function (result, value, key) {
        if (isComplexType(value)) {
          result[keyForAttribute(key)] = keyForAttribute(value);
        } else {
          result[keyForAttribute(key)] = value;
        }
      });
    } else if (Array.isArray(attribute)) {
      return attribute.map(function (attr) {
        if (isComplexType(attr)) {
          return keyForAttribute(attr);
        } else {
          return attr;
        }
      });
    } else {
      if (isFunction(opts.keyForAttribute)) {
        return opts.keyForAttribute(attribute);
      } else {
        return Inflector.caserize(attribute, opts);
      }
    }
  }

  function extractAttributes(from) {
    var dest = keyForAttribute(from.attributes || {});
    if ('id' in from) { dest[opts.id || 'id'] = from.id; }

    if (opts.typeAsAttribute) {
      if ('type' in from) { dest.type = from.type; }
    }
    if ('meta' in from) { dest.meta = keyForAttribute(from.meta || {}) }

    return dest;
  }

  function extractRelationships(from, ancestry) {
    if (!from.relationships) { return; }

    var dest = {};

    return Promise
      .all(Object.keys(from.relationships).map(function (key) {
        var relationship = from.relationships[key];

        if (relationship.data === null) {
          dest[keyForAttribute(key)] = null;
        } else if (Array.isArray(relationship.data)) {
          return Promise
            .all(relationship.data.map(function (relationshipData) {
              return extractIncludes(relationshipData, ancestry);
            }))
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        } else {
          return extractIncludes(relationship.data, ancestry)
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        }
      }))
      .then(function() {
        return dest;
      });
  }

  function extractIncludes(relationshipData, ancestry) {
    return findIncluded(relationshipData, ancestry)
      .then(function (included) {
        var valueForRelationship = getValueForRelationship(relationshipData,
          included);

        if (valueForRelationship && isFunction(valueForRelationship.then)) {
          return valueForRelationship.then(function (value) {
            return value;
          });
        } else {
          return valueForRelationship;
        }
      });
  }

  this.perform = function () {
    return Promise
      .all([extractAttributes(data), extractRelationships(data, `${data.type}${data.id}`)])
      .then(function (results) {
        var attributes = results[0];
        var relationships = results[1];
        var record = _extend(attributes, relationships);

        // Links
        if (jsonapi.links) {
          record.links = jsonapi.links;
        }

        // If option is present, transform record
        if (opts && opts.transform) {
          record = opts.transform(record);
        }

        return record;
      });
  };
};
<commit_msg>revert template literals to concatenation<commit_after>'use strict';
var isPlainObject = require('lodash/isPlainObject');
var isFunction = require('lodash/isFunction');
var _find = require('lodash/find');
var _extend = require('lodash/extend');
var _transform = require('lodash/transform');
var Inflector = require('./inflector');

module.exports = function (jsonapi, data, opts) {
  var alreadyIncluded = [];

  function isComplexType(obj) {
    return Array.isArray(obj) || isPlainObject(obj);
  }

  function getValueForRelationship(relationshipData, included) {
    if (opts && relationshipData && opts[relationshipData.type]) {
      var valueForRelationshipFct = opts[relationshipData.type]
        .valueForRelationship;

      return valueForRelationshipFct(relationshipData, included);
    } else {
      return included;
    }
  }

  function findIncluded(relationshipData, ancestry) {
    return new Promise(function (resolve) {
      if (!jsonapi.included || !relationshipData) { resolve(null); }

      var included = _find(jsonapi.included, {
        id: relationshipData.id,
        type: relationshipData.type
      });

      if (included) {
        // To prevent circular references, check if the record type
        // has already been processed in this thread
        if (ancestry.indexOf(included.type) > -1) {
          return Promise
            .all([extractAttributes(included)])
            .then(function (results) {
              var attributes = results[0];
              var relationships = results[1];
              resolve(_extend(attributes, relationships));
            });
        }

        return Promise
          .all([extractAttributes(included), extractRelationships(included, ancestry + ':' + included.type + included.id)])
          .then(function (results) {
            var attributes = results[0];
            var relationships = results[1];
            resolve(_extend(attributes, relationships));
          });
      } else {
        return resolve(null);
      }
    });
  }

  function keyForAttribute(attribute) {
    if (isPlainObject(attribute)) {
      return _transform(attribute, function (result, value, key) {
        if (isComplexType(value)) {
          result[keyForAttribute(key)] = keyForAttribute(value);
        } else {
          result[keyForAttribute(key)] = value;
        }
      });
    } else if (Array.isArray(attribute)) {
      return attribute.map(function (attr) {
        if (isComplexType(attr)) {
          return keyForAttribute(attr);
        } else {
          return attr;
        }
      });
    } else {
      if (isFunction(opts.keyForAttribute)) {
        return opts.keyForAttribute(attribute);
      } else {
        return Inflector.caserize(attribute, opts);
      }
    }
  }

  function extractAttributes(from) {
    var dest = keyForAttribute(from.attributes || {});
    if ('id' in from) { dest[opts.id || 'id'] = from.id; }

    if (opts.typeAsAttribute) {
      if ('type' in from) { dest.type = from.type; }
    }
    if ('meta' in from) { dest.meta = keyForAttribute(from.meta || {}) }

    return dest;
  }

  function extractRelationships(from, ancestry) {
    if (!from.relationships) { return; }

    var dest = {};

    return Promise
      .all(Object.keys(from.relationships).map(function (key) {
        var relationship = from.relationships[key];

        if (relationship.data === null) {
          dest[keyForAttribute(key)] = null;
        } else if (Array.isArray(relationship.data)) {
          return Promise
            .all(relationship.data.map(function (relationshipData) {
              return extractIncludes(relationshipData, ancestry);
            }))
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        } else {
          return extractIncludes(relationship.data, ancestry)
            .then(function (includes) {
              if (includes) { dest[keyForAttribute(key)] = includes; }
            });
        }
      }))
      .then(function() {
        return dest;
      });
  }

  function extractIncludes(relationshipData, ancestry) {
    return findIncluded(relationshipData, ancestry)
      .then(function (included) {
        var valueForRelationship = getValueForRelationship(relationshipData,
          included);

        if (valueForRelationship && isFunction(valueForRelationship.then)) {
          return valueForRelationship.then(function (value) {
            return value;
          });
        } else {
          return valueForRelationship;
        }
      });
  }

  this.perform = function () {
    return Promise
      .all([extractAttributes(data), extractRelationships(data, data.type + data.id)])
      .then(function (results) {
        var attributes = results[0];
        var relationships = results[1];
        var record = _extend(attributes, relationships);

        // Links
        if (jsonapi.links) {
          record.links = jsonapi.links;
        }

        // If option is present, transform record
        if (opts && opts.transform) {
          record = opts.transform(record);
        }

        return record;
      });
  };
};
<|endoftext|>


<commit_before>'use strict'
var traceHelper = require('../helpers/traceHelper')
var helper = require('../helpers/util')
var util = require('../helpers/global')

class StorageResolver {
  constructor (_traceManager) {
    this.traceManager = _traceManager
    this.clear()
  }

  /**
    * return the storage for the current context (address and vm trace index)
    * by default now returns the range 0 => 1000
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value}
    */
  storageRange (tx, stepIndex, callback) {
    storageRangeInternal(this, '0x0', 1000, tx, stepIndex, callback)
  }

  /**
    * return a slot value for the current context (address and vm trace index)
    * @param {String} - slot - slot key
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Function} - callback - {key, hashedKey, value} -
    */
  storageSlot (slot, tx, stepIndex, callback) {
    storageRangeInternal(this, slot, 100, tx, stepIndex, function (error, storage) {
      if (error) {
        callback(error)
      } else {
        var hashed = helper.sha3(slot)
        callback(null, storage[hashed] !== undefined ? storage[hashed] : null)
      }
    })
  }

  /**
    * return True if the storage at @arg address is complete
    *
    * @param {String} address  - contract address
    * @return {Bool} - return True if the storage at @arg address is complete
    */
  isComplete (address) {
    return this.storageByAddress[address] && this.storageByAddress[address].complete
  }

  /**
    * clear the cache
    *
    */
  clear () {
    this.storageByAddress = {}
  }

  /**
    * resolve the storage to the specified execution step @arg index. uses the traceManager.accumulateStorageChanges
    *
    * @param {Int} index  - execution step index
    * @param {String} address  - contract address
    * @param {Map} storage  - Map of the known storage
    * @param {Int} stepIndex  - vm trave index
    * @return {Map} - The storage resolved to the given exection point
    */
  accumulateStorageChanges (stepIndex, address, storage, callback) {
    this.traceManager.accumulateStorageChanges(stepIndex, address, storage, callback)
  }
}

function resolveAddress (self, stepIndex, callback) {
  self.traceManager.getCurrentCalledAddressAt(stepIndex, (error, result) => {
    if (error) {
      callback(error)
    } else {
      callback(null, result)
    }
  })
}

function storageRangeAtInternal (tx, address, start, maxSize, callback) {
  util.web3.debug.storageRangeAt(
    tx.blockHash, tx.transactionIndex === undefined ? tx.hash : tx.transactionIndex,
    address,
    start,
    maxSize,
    (error, result) => {
      if (error) {
        callback(error)
      } else if (result.storage) {
        callback(null, result.storage, result.complete)
      } else {
        callback('the storage has not been provided')
      }
    })
}

function storageRangeInternal (self, start, maxSize, tx, stepIndex, callback) {
  resolveAddress(self, stepIndex, (error, address) => {
    if (error) {
      callback(error)
    } else {
      if (traceHelper.isContractCreation(address)) {
        callback(null, {})
      } else {
        if (!util.web3.debug.storageRangeAt) {
          callback('no storageRangeAt endpoint found')
        } else {
          if (self.isComplete(address)) {
            var cached = fromCache(self, address)
            self.resolveStorage(stepIndex, address, cached, callback)
          } else {
            storageRangeAtInternal(tx, address, start, maxSize, (error, storage, complete) => {
              if (error) {
                callback(error)
              } else {
                toCache(self, address, storage, complete)
                self.resolveStorage(stepIndex, address, storage, callback)
              }
            })
          }
        }
      }
    }
  })
}

/**
    * retrieve the storage from the cache. if @arg slot is defined, return only the desired slot, if not return the entire known storage
    *
    * @param {String} address  - contract address
    * @param {String} slotKey  - key of the value to return
    * @return {String} - either the entire known storage or a single value
    */
function fromCache (self, address, slotKey) {
  if (!self.storageByAddress[address]) {
    return null
  }
  return slotKey ? self.storageByAddress[address].storage[slotKey] : self.storageByAddress[address].storage
}

/**
 * store the result of `storageRangeAtInternal`
 *
 * @param {String} address  - contract address
 * @param {Object} storage  - result of `storageRangeAtInternal`, contains {key, hashedKey, value}
 * @param {Bool} complete  - True if the storage is complete
 */
function toCache (self, address, storage, complete) {
  if (!self.storageByAddress[address]) {
    self.storageByAddress[address] = {}
  }
  self.storageByAddress[address].storage = Object.assign(self.storageByAddress[address].storage || {}, storage)
  if (complete !== undefined) {
    self.storageByAddress[address].complete = complete
  }
}

module.exports = StorageResolver
<commit_msg>rename resolveStorage => accumulateStorageChanges<commit_after>'use strict'
var traceHelper = require('../helpers/traceHelper')
var helper = require('../helpers/util')
var util = require('../helpers/global')

class StorageResolver {
  constructor (_traceManager) {
    this.traceManager = _traceManager
    this.clear()
  }

  /**
    * return the storage for the current context (address and vm trace index)
    * by default now returns the range 0 => 1000
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value}
    */
  storageRange (tx, stepIndex, callback) {
    storageRangeInternal(this, '0x0', 1000, tx, stepIndex, callback)
  }

  /**
    * return a slot value for the current context (address and vm trace index)
    * @param {String} - slot - slot key
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Function} - callback - {key, hashedKey, value} -
    */
  storageSlot (slot, tx, stepIndex, callback) {
    storageRangeInternal(this, slot, 100, tx, stepIndex, function (error, storage) {
      if (error) {
        callback(error)
      } else {
        var hashed = helper.sha3(slot)
        callback(null, storage[hashed] !== undefined ? storage[hashed] : null)
      }
    })
  }

  /**
    * return True if the storage at @arg address is complete
    *
    * @param {String} address  - contract address
    * @return {Bool} - return True if the storage at @arg address is complete
    */
  isComplete (address) {
    return this.storageByAddress[address] && this.storageByAddress[address].complete
  }

  /**
    * clear the cache
    *
    */
  clear () {
    this.storageByAddress = {}
  }

  /**
    * resolve the storage to the specified execution step @arg index. uses the traceManager.accumulateStorageChanges
    *
    * @param {Int} index  - execution step index
    * @param {String} address  - contract address
    * @param {Map} storage  - Map of the known storage
    * @param {Int} stepIndex  - vm trave index
    * @return {Map} - The storage resolved to the given exection point
    */
  accumulateStorageChanges (stepIndex, address, storage, callback) {
    this.traceManager.accumulateStorageChanges(stepIndex, address, storage, callback)
  }
}

function resolveAddress (self, stepIndex, callback) {
  self.traceManager.getCurrentCalledAddressAt(stepIndex, (error, result) => {
    if (error) {
      callback(error)
    } else {
      callback(null, result)
    }
  })
}

function storageRangeAtInternal (tx, address, start, maxSize, callback) {
  util.web3.debug.storageRangeAt(
    tx.blockHash, tx.transactionIndex === undefined ? tx.hash : tx.transactionIndex,
    address,
    start,
    maxSize,
    (error, result) => {
      if (error) {
        callback(error)
      } else if (result.storage) {
        callback(null, result.storage, result.complete)
      } else {
        callback('the storage has not been provided')
      }
    })
}

function storageRangeInternal (self, start, maxSize, tx, stepIndex, callback) {
  resolveAddress(self, stepIndex, (error, address) => {
    if (error) {
      callback(error)
    } else {
      if (traceHelper.isContractCreation(address)) {
        callback(null, {})
      } else {
        if (!util.web3.debug.storageRangeAt) {
          callback('no storageRangeAt endpoint found')
        } else {
          if (self.isComplete(address)) {
            var cached = fromCache(self, address)
            self.accumulateStorageChanges(stepIndex, address, cached, callback)
          } else {
            storageRangeAtInternal(tx, address, start, maxSize, (error, storage, complete) => {
              if (error) {
                callback(error)
              } else {
                toCache(self, address, storage, complete)
                self.accumulateStorageChanges(stepIndex, address, storage, callback)
              }
            })
          }
        }
      }
    }
  })
}

/**
    * retrieve the storage from the cache. if @arg slot is defined, return only the desired slot, if not return the entire known storage
    *
    * @param {String} address  - contract address
    * @param {String} slotKey  - key of the value to return
    * @return {String} - either the entire known storage or a single value
    */
function fromCache (self, address, slotKey) {
  if (!self.storageByAddress[address]) {
    return null
  }
  return slotKey ? self.storageByAddress[address].storage[slotKey] : self.storageByAddress[address].storage
}

/**
 * store the result of `storageRangeAtInternal`
 *
 * @param {String} address  - contract address
 * @param {Object} storage  - result of `storageRangeAtInternal`, contains {key, hashedKey, value}
 * @param {Bool} complete  - True if the storage is complete
 */
function toCache (self, address, storage, complete) {
  if (!self.storageByAddress[address]) {
    self.storageByAddress[address] = {}
  }
  self.storageByAddress[address].storage = Object.assign(self.storageByAddress[address].storage || {}, storage)
  if (complete !== undefined) {
    self.storageByAddress[address].complete = complete
  }
}

module.exports = StorageResolver
<|endoftext|>


<commit_before>const EventEmitter = require('events');

const moment = require('moment');
const CouchbaseChannelDisney = require('./couchbaseChannelDisney');
const Facilities = require('./disneyFacilityChannel');
const DebugLog = require('../debugPrint.js');
const Cache = require('../cache');

const DisneyUtil = require('./disneyUtil');

const sParkID = Symbol('Park ID');
const sChannel = Symbol('Channel');
const sFacilities = Symbol('Facilities');
const sBusVersion = Symbol('Bus Version');
const sPollInterval = Symbol('Polling Interval');

class DisneyLiveBusTimes extends EventEmitter {
  constructor(options = {}) {
    super();

    this[sParkID] = options.park_id === undefined ? 'wdw' : options.park_id;
    this[sBusVersion] = options.version === undefined ? '1_0' : options.version;

    // how often to poll for bus time updates (in seconds)
    this[sPollInterval] = options.pollinterval === undefined ? 10 : options.pollinterval;

    // setup our facilities channel, we need this to sync before we get our bus times
    this[sFacilities] = new Facilities({
      resort_Id: this[sParkID],
    });

    // NOTE - whilst there is a live channel for "Outpost Depot at Disney's Fort Wilderness Resort & Campground"
    //  our list of resorts doesn't return the ID 144943 needed to subscribe
    //  However, channel 80010408 has exactly the same data, so we're getting it via the "The Cabins at Disney's Fort Wilderness Resort" resort instead
  }

  /**
   * Callback for "updated" event from our couchbase channel class
   * @param {Object} doc
   */
  OnDocUpdated(doc) {
    // console.log(JSON.stringify(doc, null, 2));
    const id = DisneyUtil.CleanID(doc.id);

    this[sFacilities].GetFacilityName(id).then((name) => {
      if (doc.destinations === undefined) {
        return;
      }

      // add human-readable times and names to each destination
      const destinationPromises = [];
      doc.destinations.forEach((dest) => {
        destinationPromises.push(this.ParseDestination(dest));
      });
      Promise.all(destinationPromises).then((destinations) => {
        // emit events for each updated bus stop
        // console.log(JSON.stringify(destinations, null, 2));

        destinations.forEach((dest) => {
          if (dest.arrivals !== undefined) {
            dest.arrivals.forEach((arrival) => {
              this.emit('busupdate', {
                from: name,
                from_id: id,
                to: dest.name,
                to_id: DisneyUtil.CleanID(dest.id),
                atStop: arrival.atStop,
                atStopHuman: arrival.atStop_human,
                atDestination: arrival.atDestination,
                atDestinationHuman: arrival.atDestination_human,
                frequency: dest.frequency,
                frequencyHuman: dest.frequency_human,
              });
            });
          } else {
            // emit event with boring "every 20 minutes" update
            this.emit('busupdate', {
              from: name,
              from_id: id,
              to: dest.name,
              to_id: DisneyUtil.CleanID(dest.id),
              frequency: dest.frequency,
              frequencyHuman: dest.frequency_human,
            });
          }
        });
      });
    });
  }

  ParseDestination(destination) {
    return this[sFacilities].GetFacilityName(destination.id).then((name) => {
      destination.name = name;

      // use moment to make some human-readable times
      const now = moment();

      if (destination.frequency !== undefined) {
        const busFrequency = now.clone().add(destination.frequency, 'seconds');
        destination.frequency_human = `every ${busFrequency.from(now, true)}`;
      }

      // add human-readable strings to each arrival
      if (destination.arrivals !== undefined) {
        destination.arrivals.forEach((arrival) => {
          arrival.atStop_human = moment(arrival.atStop).from(now);
          arrival.atDestination_human = moment(arrival.atDestination).from(now);
        });
      }

      return Promise.resolve(destination);
    });
  }

  /**
   * Start fetching live bus time updates
   */
  Start() {
    if (!this[sFacilities].Synced) {
      // start facilities syncing,
      return this[sFacilities].Start().then(() => {
        if (this[sChannel] === undefined) {
          // set this to something in case multiple callbacks arrive before we set it up
          this[sChannel] = 1;

          // find all our resorts and build channel names
          return this.GetChannelNames().then((channels) => {
            // create our channel for fetching bus times
            this[sChannel] = new CouchbaseChannelDisney({
              dbName: `${this[sParkID]}_buses`,
              channel: channels.join(','),
              longpollDelay: 10, // delay each longpoll by 10 seconds (to avoid constant flooding of updates)
            });

            // setup puller hooks for document updates
            this[sChannel].on('updated', this.OnDocUpdated.bind(this));
            this[sChannel].on('error', this.Log.bind(this));

            // start the channel fetching documents
            this[sChannel].Start();
          });
        }

        return Promise.resolve();
      });
    }

    return Promise.resolve();
  }

  GetChannelNames() {
    return Cache.DB().then(db => new Promise((resolve, reject) => {
      db.all('SELECT id, name FROM disneyFacilities WHERE entityType = ? AND park = ?', ['resort', this[sParkID]], (err, rows) => {
        if (err) {
          return reject(err);
        }

        const channels = [];
        rows.forEach((row) => {
          channels.push(`${this[sParkID]}.arrivals.${row.id};entityType=resort.${this[sBusVersion]}`);
        });

        return resolve(channels);
      });
    }));
  }

  /**
   * Debug print a message (when NODE_DEBUG=themeparks is set in environment)
   * @param {...*} ToPrint Objects/strings to print
   * */
  Log(...args) {
    return DebugLog.apply(null, [`${this.constructor.name}:`, ...args]);
  }
}

module.exports = DisneyLiveBusTimes;
<commit_msg>[!] Fix typo passing in resort ID<commit_after>const EventEmitter = require('events');

const moment = require('moment');
const CouchbaseChannelDisney = require('./couchbaseChannelDisney');
const Facilities = require('./disneyFacilityChannel');
const DebugLog = require('../debugPrint.js');
const Cache = require('../cache');

const DisneyUtil = require('./disneyUtil');

const sParkID = Symbol('Park ID');
const sChannel = Symbol('Channel');
const sFacilities = Symbol('Facilities');
const sBusVersion = Symbol('Bus Version');
const sPollInterval = Symbol('Polling Interval');

class DisneyLiveBusTimes extends EventEmitter {
  constructor(options = {}) {
    super();

    this[sParkID] = options.park_id === undefined ? 'wdw' : options.park_id;
    this[sBusVersion] = options.version === undefined ? '1_0' : options.version;

    // how often to poll for bus time updates (in seconds)
    this[sPollInterval] = options.pollinterval === undefined ? 10 : options.pollinterval;

    // setup our facilities channel, we need this to sync before we get our bus times
    this[sFacilities] = new Facilities({
      resort_id: this[sParkID],
    });

    // NOTE - whilst there is a live channel for "Outpost Depot at Disney's Fort Wilderness Resort & Campground"
    //  our list of resorts doesn't return the ID 144943 needed to subscribe
    //  However, channel 80010408 has exactly the same data, so we're getting it via the "The Cabins at Disney's Fort Wilderness Resort" resort instead
  }

  /**
   * Callback for "updated" event from our couchbase channel class
   * @param {Object} doc
   */
  OnDocUpdated(doc) {
    // console.log(JSON.stringify(doc, null, 2));
    const id = DisneyUtil.CleanID(doc.id);

    this[sFacilities].GetFacilityName(id).then((name) => {
      if (doc.destinations === undefined) {
        return;
      }

      // add human-readable times and names to each destination
      const destinationPromises = [];
      doc.destinations.forEach((dest) => {
        destinationPromises.push(this.ParseDestination(dest));
      });
      Promise.all(destinationPromises).then((destinations) => {
        // emit events for each updated bus stop
        // console.log(JSON.stringify(destinations, null, 2));

        destinations.forEach((dest) => {
          if (dest.arrivals !== undefined) {
            dest.arrivals.forEach((arrival) => {
              this.emit('busupdate', {
                from: name,
                from_id: id,
                to: dest.name,
                to_id: DisneyUtil.CleanID(dest.id),
                atStop: arrival.atStop,
                atStopHuman: arrival.atStop_human,
                atDestination: arrival.atDestination,
                atDestinationHuman: arrival.atDestination_human,
                frequency: dest.frequency,
                frequencyHuman: dest.frequency_human,
              });
            });
          } else {
            // emit event with boring "every 20 minutes" update
            this.emit('busupdate', {
              from: name,
              from_id: id,
              to: dest.name,
              to_id: DisneyUtil.CleanID(dest.id),
              frequency: dest.frequency,
              frequencyHuman: dest.frequency_human,
            });
          }
        });
      });
    });
  }

  ParseDestination(destination) {
    return this[sFacilities].GetFacilityName(destination.id).then((name) => {
      destination.name = name;

      // use moment to make some human-readable times
      const now = moment();

      if (destination.frequency !== undefined) {
        const busFrequency = now.clone().add(destination.frequency, 'seconds');
        destination.frequency_human = `every ${busFrequency.from(now, true)}`;
      }

      // add human-readable strings to each arrival
      if (destination.arrivals !== undefined) {
        destination.arrivals.forEach((arrival) => {
          arrival.atStop_human = moment(arrival.atStop).from(now);
          arrival.atDestination_human = moment(arrival.atDestination).from(now);
        });
      }

      return Promise.resolve(destination);
    });
  }

  /**
   * Start fetching live bus time updates
   */
  Start() {
    if (!this[sFacilities].Synced) {
      // start facilities syncing,
      return this[sFacilities].Start().then(() => {
        if (this[sChannel] === undefined) {
          // set this to something in case multiple callbacks arrive before we set it up
          this[sChannel] = 1;

          // find all our resorts and build channel names
          return this.GetChannelNames().then((channels) => {
            // create our channel for fetching bus times
            this[sChannel] = new CouchbaseChannelDisney({
              dbName: `${this[sParkID]}_buses`,
              channel: channels.join(','),
              longpollDelay: 10, // delay each longpoll by 10 seconds (to avoid constant flooding of updates)
            });

            // setup puller hooks for document updates
            this[sChannel].on('updated', this.OnDocUpdated.bind(this));
            this[sChannel].on('error', this.Log.bind(this));

            // start the channel fetching documents
            this[sChannel].Start();
          });
        }

        return Promise.resolve();
      });
    }

    return Promise.resolve();
  }

  GetChannelNames() {
    return Cache.DB().then(db => new Promise((resolve, reject) => {
      db.all('SELECT id, name FROM disneyFacilities WHERE entityType = ? AND park = ?', ['resort', this[sParkID]], (err, rows) => {
        if (err) {
          return reject(err);
        }

        const channels = [];
        rows.forEach((row) => {
          channels.push(`${this[sParkID]}.arrivals.${row.id};entityType=resort.${this[sBusVersion]}`);
        });

        return resolve(channels);
      });
    }));
  }

  /**
   * Debug print a message (when NODE_DEBUG=themeparks is set in environment)
   * @param {...*} ToPrint Objects/strings to print
   * */
  Log(...args) {
    return DebugLog.apply(null, [`${this.constructor.name}:`, ...args]);
  }
}

module.exports = DisneyLiveBusTimes;
<|endoftext|>


<commit_before>'use strict';

const delegate = require('delegates');
const co = require('co');
const { assign } = require('utility');

const HELPER = Symbol('Context#helper');
const LOCALS = Symbol('Context#locals');
const LOCALS_LIST = Symbol('Context#localsList');
const COOKIES = Symbol('Context#cookies');
const CONTEXT_LOGGERS = Symbol('Context#logger');
const CONTEXT_HTTPCLIENT = Symbol('Context#httpclient');

const proto = module.exports = {
  get cookies() {
    if (!this[COOKIES]) {
      this[COOKIES] = new this.app.ContextCookies(this, this.app.keys);
    }
    return this[COOKIES];
  },

  /**
   * Get a wrapper httpclient instance contain ctx in the hold request process
   *
   * @return {ContextHttpClient} the wrapper httpclient instance
   */
  get httpclient() {
    if (!this[CONTEXT_HTTPCLIENT]) {
      this[CONTEXT_HTTPCLIENT] = new this.app.ContextHttpClient(this);
    }
    return this[CONTEXT_HTTPCLIENT];
  },

  /**
   * Shortcut for httpclient.curl
   *
   * @method Context#curl
   * @param {String|Object} url - request url address.
   * @param {Object} [options] - options for request.
   * @return {Object} see {@link ContextHttpClient#curl}
   */
  curl(url, options) {
    return this.httpclient.curl(url, options);
  },

  /**
   * Alias to {@link Application#router}
   *
   * @member {Router} Context#router
   * @since 1.0.0
   * @example
   * ```js
   * this.router.pathFor('post', { id: 12 });
   * ```
   */
  get router() {
    return this.app.router;
  },

  /**
   * Get helper instance from {@link Application#Helper}
   *
   * @member {Helper} Context#helper
   * @since 1.0.0
   */
  get helper() {
    if (!this[HELPER]) {
      this[HELPER] = new this.app.Helper(this);
    }
    return this[HELPER];
  },

  /**
   * Wrap app.loggers with context infomation,
   * if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`
   *
   * @param {String} name - logger name
   * @return {Logger} logger
   */
  getLogger(name) {
    let cache = this[CONTEXT_LOGGERS];
    if (!cache) {
      cache = this[CONTEXT_LOGGERS] = {};
    }

    // read from cache
    if (cache[name]) return cache[name];

    // get no exist logger
    const appLogger = this.app.getLogger(name);
    if (!appLogger) return null;

    // write to cache
    cache[name] = new this.app.ContextLogger(this, appLogger);
    return cache[name];
  },

  /**
   * Logger for Application, wrapping app.coreLogger with context infomation
   *
   * @member {ContextLogger} Context#logger
   * @since 1.0.0
   * @example
   * ```js
   * this.logger.info('some request data: %j', this.request.body);
   * this.logger.warn('WARNING!!!!');
   * ```
   */
  get logger() {
    return this.getLogger('logger');
  },

  /**
   * Logger for frameworks and plugins,
   * wrapping app.coreLogger with context infomation
   *
   * @member {ContextLogger} Context#coreLogger
   * @since 1.0.0
   */
  get coreLogger() {
    return this.getLogger('coreLogger');
  },

  /**
   * locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,
   * which will be used as data when view is rendering.
   * The difference between `app.locals` and `ctx.locals` is the context level, `app.locals` is global level, and `ctx.locals` is request level. when you get `ctx.locals`, it will merge `app.locals`.
   *
   * when you set locals, only object is available
   *
   * ```js
   * this.locals = {
   *   a: 1
   * };
   * this.locals = {
   *   b: 1
   * };
   * this.locals.c = 1;
   * console.log(this.locals);
   * {
   *   a: 1,
   *   b: 1,
   *   c: 1,
   * };
   * ```
   *
   * `ctx.locals` has cache, it only merges `app.locals` once in one request.
   *
   * @member {Object} Context#locals
   */
  get locals() {
    if (!this[LOCALS]) {
      this[LOCALS] = assign({}, this.app.locals);
    }
    if (this[LOCALS_LIST] && this[LOCALS_LIST].length) {
      assign(this[LOCALS], this[LOCALS_LIST]);
      this[LOCALS_LIST] = null;
    }
    return this[LOCALS];
  },

  set locals(val) {
    if (!this[LOCALS_LIST]) {
      this[LOCALS_LIST] = [];
    }
    this[LOCALS_LIST].push(val);
  },

  /**
   * alias to {@link Context#locals}, compatible with koa that use this variable
   * @member {Object} state
   * @see Context#locals
   */
  get state() {
    return this.locals;
  },

  set state(val) {
    this.locals = val;
  },

  /**
   * Run generator function in the background
   * @param {Generator} scope - generator function, the first args is ctx
   * ```js
   * this.body = 'hi';
   *
   * this.runInBackground(function* saveUserInfo(ctx) {
   *   yield ctx.mysql.query(sql);
   *   yield ctx.curl(url);
   * });
   * ```
   */
  runInBackground(scope) {
    const ctx = this;
    const start = Date.now();
    /* istanbul ignore next */
    const taskName = scope.name || '-';
    co(function* () {
      yield scope(ctx);
      ctx.coreLogger.info('[egg:background] task:%s success (%dms)', taskName, Date.now() - start);
    }).catch(err => {
      ctx.coreLogger.info('[egg:background] task:%s fail (%dms)', taskName, Date.now() - start);
      ctx.coreLogger.error(err);
    });
  },
};

/**
 * Context delegation.
 */

delegate(proto, 'request')
  /**
   * @member {Boolean} Context#acceptJSON
   * @see Request#acceptJSON
   * @since 1.0.0
   */
  .getter('acceptJSON')
  /**
   * @member {Array} Context#queries
   * @see Request#queries
   * @since 1.0.0
   */
  .getter('queries')
  /**
   * @member {Boolean} Context#accept
   * @see Request#accept
   * @since 1.0.0
   */
  .getter('accept')
  /**
   * @member {string} Context#ip
   * @see Request#ip
   * @since 1.0.0
   */
  .access('ip');

delegate(proto, 'response')
  /**
   * @member {Void} Context#jsonp
   * @see Response#jsonp
   * @since 1.0.0
   */
  .setter('jsonp')
  /**
   * @member {Number} Context#realStatus
   * @see Response#realStatus
   * @since 1.0.0
   */
  .access('realStatus');
<commit_msg>fix: remove unused jsonp delegate (#739)<commit_after>'use strict';

const delegate = require('delegates');
const co = require('co');
const { assign } = require('utility');

const HELPER = Symbol('Context#helper');
const LOCALS = Symbol('Context#locals');
const LOCALS_LIST = Symbol('Context#localsList');
const COOKIES = Symbol('Context#cookies');
const CONTEXT_LOGGERS = Symbol('Context#logger');
const CONTEXT_HTTPCLIENT = Symbol('Context#httpclient');

const proto = module.exports = {
  get cookies() {
    if (!this[COOKIES]) {
      this[COOKIES] = new this.app.ContextCookies(this, this.app.keys);
    }
    return this[COOKIES];
  },

  /**
   * Get a wrapper httpclient instance contain ctx in the hold request process
   *
   * @return {ContextHttpClient} the wrapper httpclient instance
   */
  get httpclient() {
    if (!this[CONTEXT_HTTPCLIENT]) {
      this[CONTEXT_HTTPCLIENT] = new this.app.ContextHttpClient(this);
    }
    return this[CONTEXT_HTTPCLIENT];
  },

  /**
   * Shortcut for httpclient.curl
   *
   * @method Context#curl
   * @param {String|Object} url - request url address.
   * @param {Object} [options] - options for request.
   * @return {Object} see {@link ContextHttpClient#curl}
   */
  curl(url, options) {
    return this.httpclient.curl(url, options);
  },

  /**
   * Alias to {@link Application#router}
   *
   * @member {Router} Context#router
   * @since 1.0.0
   * @example
   * ```js
   * this.router.pathFor('post', { id: 12 });
   * ```
   */
  get router() {
    return this.app.router;
  },

  /**
   * Get helper instance from {@link Application#Helper}
   *
   * @member {Helper} Context#helper
   * @since 1.0.0
   */
  get helper() {
    if (!this[HELPER]) {
      this[HELPER] = new this.app.Helper(this);
    }
    return this[HELPER];
  },

  /**
   * Wrap app.loggers with context infomation,
   * if a custom logger is defined by naming aLogger, then you can `ctx.getLogger('aLogger')`
   *
   * @param {String} name - logger name
   * @return {Logger} logger
   */
  getLogger(name) {
    let cache = this[CONTEXT_LOGGERS];
    if (!cache) {
      cache = this[CONTEXT_LOGGERS] = {};
    }

    // read from cache
    if (cache[name]) return cache[name];

    // get no exist logger
    const appLogger = this.app.getLogger(name);
    if (!appLogger) return null;

    // write to cache
    cache[name] = new this.app.ContextLogger(this, appLogger);
    return cache[name];
  },

  /**
   * Logger for Application, wrapping app.coreLogger with context infomation
   *
   * @member {ContextLogger} Context#logger
   * @since 1.0.0
   * @example
   * ```js
   * this.logger.info('some request data: %j', this.request.body);
   * this.logger.warn('WARNING!!!!');
   * ```
   */
  get logger() {
    return this.getLogger('logger');
  },

  /**
   * Logger for frameworks and plugins,
   * wrapping app.coreLogger with context infomation
   *
   * @member {ContextLogger} Context#coreLogger
   * @since 1.0.0
   */
  get coreLogger() {
    return this.getLogger('coreLogger');
  },

  /**
   * locals is an object for view, you can use `app.locals` and `ctx.locals` to set variables,
   * which will be used as data when view is rendering.
   * The difference between `app.locals` and `ctx.locals` is the context level, `app.locals` is global level, and `ctx.locals` is request level. when you get `ctx.locals`, it will merge `app.locals`.
   *
   * when you set locals, only object is available
   *
   * ```js
   * this.locals = {
   *   a: 1
   * };
   * this.locals = {
   *   b: 1
   * };
   * this.locals.c = 1;
   * console.log(this.locals);
   * {
   *   a: 1,
   *   b: 1,
   *   c: 1,
   * };
   * ```
   *
   * `ctx.locals` has cache, it only merges `app.locals` once in one request.
   *
   * @member {Object} Context#locals
   */
  get locals() {
    if (!this[LOCALS]) {
      this[LOCALS] = assign({}, this.app.locals);
    }
    if (this[LOCALS_LIST] && this[LOCALS_LIST].length) {
      assign(this[LOCALS], this[LOCALS_LIST]);
      this[LOCALS_LIST] = null;
    }
    return this[LOCALS];
  },

  set locals(val) {
    if (!this[LOCALS_LIST]) {
      this[LOCALS_LIST] = [];
    }
    this[LOCALS_LIST].push(val);
  },

  /**
   * alias to {@link Context#locals}, compatible with koa that use this variable
   * @member {Object} state
   * @see Context#locals
   */
  get state() {
    return this.locals;
  },

  set state(val) {
    this.locals = val;
  },

  /**
   * Run generator function in the background
   * @param {Generator} scope - generator function, the first args is ctx
   * ```js
   * this.body = 'hi';
   *
   * this.runInBackground(function* saveUserInfo(ctx) {
   *   yield ctx.mysql.query(sql);
   *   yield ctx.curl(url);
   * });
   * ```
   */
  runInBackground(scope) {
    const ctx = this;
    const start = Date.now();
    /* istanbul ignore next */
    const taskName = scope.name || '-';
    co(function* () {
      yield scope(ctx);
      ctx.coreLogger.info('[egg:background] task:%s success (%dms)', taskName, Date.now() - start);
    }).catch(err => {
      ctx.coreLogger.info('[egg:background] task:%s fail (%dms)', taskName, Date.now() - start);
      ctx.coreLogger.error(err);
    });
  },
};

/**
 * Context delegation.
 */

delegate(proto, 'request')
  /**
   * @member {Boolean} Context#acceptJSON
   * @see Request#acceptJSON
   * @since 1.0.0
   */
  .getter('acceptJSON')
  /**
   * @member {Array} Context#queries
   * @see Request#queries
   * @since 1.0.0
   */
  .getter('queries')
  /**
   * @member {Boolean} Context#accept
   * @see Request#accept
   * @since 1.0.0
   */
  .getter('accept')
  /**
   * @member {string} Context#ip
   * @see Request#ip
   * @since 1.0.0
   */
  .access('ip');

delegate(proto, 'response')
  /**
   * @member {Number} Context#realStatus
   * @see Response#realStatus
   * @since 1.0.0
   */
  .access('realStatus');
<|endoftext|>


<commit_before>/*
    Facility to cache test results in localStorage.

    USAGE:
      cache.get('key');
      cache.set('key', 'value');
 */

function readLocalStorage(key) {
  if (!document.hasFocus()) {
    // if the document does not have focus when tests are executed, focus() may
    // not be handled properly and eventy may not be dispatched immediately.
    // This can happen when a document is reloaded while Developer Tools have focus.
    /*eslint-disable no-console */
    window.console && window.console.warn && console.warn('document requires focus for a11y support tests');
    /*eslint-enable no-console */
    return {};
  }

  let data;

  try {
    data = window.localStorage && window.localStorage.getItem(key);
    data = data ? JSON.parse(data) : {};
  } catch (e) {
    data = {};
  }

  return data;
}

function writeLocalStorage(key, value) {
  if (!document.hasFocus()) {
    // if the document does not have focus when tests are executed, focus() may
    // not be handled properly and eventy may not be dispatched immediately.
    // This can happen when a document is reloaded while Developer Tools have focus.
    try {
      window.localStorage && window.localStorage.removeItem(key);
    } catch(e) {
      // ignore
    }

    return;
  }

  try {
    window.localStorage && window.localStorage.setItem(key, JSON.stringify(value));
  } catch (e) {
    // ignore
  }
}

const userAgent = window.navigator.userAgent;
const cacheKey = 'ally-supports-cache';
let cache = readLocalStorage(cacheKey);

// update the cache if the user agent changes (newer version, etc)
if (cache.userAgent !== userAgent) {
  cache = {};
}

cache.userAgent = userAgent;

export default {
  get: function getCacheValue(key) {
    return cache[key];
  },
  set: function setCacheValue(key, value) {
    cache[key] = value;
    writeLocalStorage(cacheKey, cache);
  },
};
<commit_msg>fix(supports/cache): allow reading while document does not have focus<commit_after>/*
    Facility to cache test results in localStorage.

    USAGE:
      cache.get('key');
      cache.set('key', 'value');
 */

function readLocalStorage(key) {
  // allow reading from storage to retrieve previous support results
  // even while the document does not have focus
  let data;

  try {
    data = window.localStorage && window.localStorage.getItem(key);
    data = data ? JSON.parse(data) : {};
  } catch (e) {
    data = {};
  }

  return data;
}

function writeLocalStorage(key, value) {
  if (!document.hasFocus()) {
    // if the document does not have focus when tests are executed, focus() may
    // not be handled properly and events may not be dispatched immediately.
    // This can happen when a document is reloaded while Developer Tools have focus.
    try {
      window.localStorage && window.localStorage.removeItem(key);
    } catch(e) {
      // ignore
    }

    return;
  }

  try {
    window.localStorage && window.localStorage.setItem(key, JSON.stringify(value));
  } catch (e) {
    // ignore
  }
}

const userAgent = window.navigator.userAgent;
const cacheKey = 'ally-supports-cache';
let cache = readLocalStorage(cacheKey);

// update the cache if the user agent changes (newer version, etc)
if (cache.userAgent !== userAgent) {
  cache = {};
}

cache.userAgent = userAgent;

export default {
  get: function getCacheValue(key) {
    return cache[key];
  },
  set: function setCacheValue(key, value) {
    cache[key] = value;
    writeLocalStorage(cacheKey, cache);
  },
};
<|endoftext|>


<commit_before>import React from 'react'
const el = React.createElement
import { observer } from 'mobservable-react'
import {get} from 'lodash'
import {labeledPart} from '../form'

export default function ({view, condition}) {
  var conditionPath = condition.path

  return function (collections, collectionId, itemId, $patch) {
    var model = collections[collectionId].model
    var cmp = (typeof view === 'function' ? view : labeledPart)(collections, collectionId, itemId, $patch, view)

    return observer(function () {
      var fieldValue = $patch.has(conditionPath) ?
        $patch.get(conditionPath) :
        get(model.get(itemId).value, conditionPath)
      if (fieldValue === condition.value) return el(cmp) // TODO: better condition evalutation
      return null
    })
  }
}
<commit_msg>displayIf as form part enhanced with more powerful condition<commit_after>import React from 'react'
const el = React.createElement
import { observer } from 'mobservable-react'
import {get} from 'lodash'
import {labeledPart} from '../form'

export default function ({view, condition}) {
  return function (collections, collectionId, itemId, $patch) {
    var model = collections[collectionId].model
    var shouldDisplay

    if (typeof(condition) !== 'function') {
      var conditionPath = condition.path
      var conditionValue = condition.value
      shouldDisplay = function() {
        var fieldValue = $patch.has(conditionPath) ?
          $patch.get(conditionPath)
          : get(model.get(itemId).value, conditionPath)

        return fieldValue === conditionValue
      }
    } else {
      shouldDisplay = () => condition(model.get(itemId).value, collections, collectionId, itemId, $patch)
    }

    var cmp = (typeof view === 'function' ? view : labeledPart)(collections, collectionId, itemId, $patch, view)

    return observer(function () {
      if (shouldDisplay()) return el(cmp) // TODO: better condition evalutation
      return null
    })
  }
}
<|endoftext|>


<commit_before>/* globals jasmine */

'use strict';

var sequencer = require('../../main/sequencer'),
  playbackSlotMock = require('./playbackSlotMock'),
  defer = require('./defer'),
  describe = jasmine.getEnv().describe,
  beforeEach = jasmine.getEnv().beforeEach,
  it = jasmine.getEnv().it,
  expect = jasmine.getEnv().expect,
  defaults = require('lodash/object/defaults'),
  after = require('lodash/function/after'),
  times = require('lodash/utility/times'),
  identity = require('lodash/utility/identity'),
  last = require('lodash/array/last'),
  initial = require('lodash/array/initial'),
  contains = require('lodash/collection/contains');

describe('A sequencer', function() {

  /**
   * @param {function(Object)=} inter
   * @returns {Sequencer}
   */
  function sequencerWithDefaults(inter) {
    inter = inter || identity;

    var defaultConfig = {
      nextEntryProducer: function(entry) {
        return null;
      },
      playbackSlotProducer: function(config) {
        var slot = playbackSlotMock({
          entry: config.entry,
          video: config.entry.video
        });
        slot.load.and.returnValue(Promise.resolve());
        slot.end.and.returnValue(Promise.resolve());
        return slot;
      },
      comingNext: jasmine.createSpy('comingNextSpy'),
      stateChanged: jasmine.createSpy('stateChangedSpy'),
      loadingChanged: jasmine.createSpy('loadingChangedSpy'),
      loadFailed: jasmine.createSpy('loadFailedSpy')
    };
    return sequencer(defaults({}, inter(defaultConfig), defaultConfig));
  }

  var _entries,
    _nextEntryProducer;

  beforeEach(function() {
    _entries = times(5, function(idx) {
      var id = 'mockEntry' + idx;
      return {
        id: id,
        video: {provider: 'mock', id: 'video-' + id}
      };
    });

    _nextEntryProducer = function(entry) {
      var idx = 0;
      if (entry) {
        idx = _entries.indexOf(entry) + 1;
      }
      if (idx >= _entries.length) {
        return null;
      }
      return _entries[idx];
    };
  });

  it('does not call nextEntryProducer first call to play', function(done) {
    var nextEntryProducerSpy = jasmine.createSpy('nextEntryProducerSpy');
    var seq = sequencerWithDefaults(function() {
      return {
        nextEntryProducer: nextEntryProducerSpy
      };
    });

    seq.play();

    defer(function() {
      expect(nextEntryProducerSpy).not.toHaveBeenCalledWith(null);

      done();
    });
  });

  it('executes the right sequence when manually skipping to an entry', function(done) {
    var nextEntryProducerSpy =
        jasmine.createSpy('nextEntryProducerSpy')
          .and.callFake(_nextEntryProducer),

      playbackSlotProducerSpy = jasmine.createSpy('nextEntryProducerSpy');

    var seq = sequencerWithDefaults(function(seqDefaultCfg) {
      return {
        nextEntryProducer: nextEntryProducerSpy,
        playbackSlotProducer: playbackSlotProducerSpy
          .and.callFake(function(producerCfg) {
            finishGate();
            return seqDefaultCfg.playbackSlotProducer(producerCfg);
          })
      };
    });

    var expectedSlotProducerCallsCount = 4,
      finishGate = after(expectedSlotProducerCallsCount, function() {

        expect(nextEntryProducerSpy.calls.allArgs()).toEqual([
          // asked for the next entry after the entry 1
          [_entries[0]],
          // asked for the next entry after the entry 3
          [_entries[3]]
        ]);

        var slotLoadedEntries = playbackSlotProducerSpy.calls.allArgs()
          .map(function(args) {
            return args[0].entry;
          });

        expect(slotLoadedEntries).toEqual([
          // play from pristine state
          _entries[0],
          // preload next after entry 1 playing
          _entries[1],
          // manually skipped to entry 3
          _entries[3],
          // preload next after entry 3 playing
          _entries[4]
        ]);

        defer(done);
      });

    seq.skip(_entries[0]);
    seq.play();

    defer(function() {
      seq.skip(_entries[3]);
    });
  });

  it('schedules the preloaded entry properly so that it gets played on automated ending', function(done) {

    var comingNextSpy,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {

        comingNextSpy = seqDefaultCfg.comingNext;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            // fake auto ending
            slot.start.and.callFake(function() {
              defer(function() {
                producerCfg.endingSoon();
                defer(function() {
                  producerCfg.ending();
                  finishGate();
                });
              });
            });

            return slot;
          }
        };
      });

    var finishGate = after(_entries.length, function() {

      expect(comingNextSpy.calls.count()).toEqual(_entries.length);

      done();
    });

    seq.play();
    seq.skip(_entries[0]);
  });

  it('keeps the correct entry to play after many consecutive skip calls', function(done) {

    var slots = [];

    var seq = sequencerWithDefaults(function(seqDefaultCfg) {
      return {
        nextEntryProducer: _nextEntryProducer,
        playbackSlotProducer: function(producerCfg) {
          var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
          slots.push(slot);
          return slot;
        }
      };
    });

    seq.play();

    // browses the list of entry and skip until the last one
    new Promise(function(resolve) {
      (function deferredWhile(idx) {
        if (idx < _entries.length) {
          defer(function() {
            seq.skip(_entries[idx]);
            deferredWhile(idx + 1);
          });
        } else {
          resolve();
        }
      })(0);
    }).then(function() {

        // all but the last slot (the one playing right now with no next entry) should have been ended

        initial(slots).forEach(function(slot) {
          expect(slot.end).toHaveBeenCalled();
        });

        expect(last(slots).end).not.toHaveBeenCalled();
        expect(last(slots).entry).toEqual(last(_entries));

        done();
      });
  });

  it('calls comingNext properly when skip is called', function(done) {
    var comingNextSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        comingNextSpy = seqDefaultCfg.comingNext.and.callFake(function() {
          defer(runChecks);
        });

        return {
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            slot.end.and.callFake(function() {
              defer(function() {
                producerCfg.endingSoon();
              });
            });
            return slot;
          }
        };
      });

    seq.skip(_entries[0]);
    seq.play();

    defer(function() {
      seq.skip(_entries[1]);
    });

    function runChecks() {
      expect(comingNextSpy).toHaveBeenCalledWith(_entries[0].video, _entries[1].video);

      done();
    }
  });

  it('pauses and resumes properly', function(done) {
    var slot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          playbackSlotProducer: function(producerCfg) {
            slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            slot.load.and.callFake(function() {
              defer(finish);
              return Promise.resolve();
            });

            return slot;
          }
        };
      });

    seq.skip(_entries[0]);

    // by default slots should be suspended until play is called
    expect(slot.suspend).toHaveBeenCalled();

    seq.play();

    function finish() {

      expect(slot.proceed).toHaveBeenCalled();

      done();
    }
  });

  it('it resumes properly if skip was called while paused', function(done) {
    var slots = [],
      slotsIdx = 0,
      steps = [step1, step2],
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            slots[slotsIdx] = slot;
            var step = steps[slotsIdx];

            slot.load.and.callFake(function() {
              defer(step);
              return Promise.resolve();
            });

            slotsIdx++;

            return slot;
          }
        };
      });

    seq.play();
    seq.pause();
    seq.skip(_entries[0]);

    function step1() {
      seq.skip(_entries[1]);
    }

    function step2() {
      seq.play();

      defer(function() {

        expect(slots[0].proceed).not.toHaveBeenCalled();
        expect(slots[1].proceed).toHaveBeenCalled();

        done();
      });
    }
  });

  it('calls the loadFailed callback when en entry failed to load', function(done) {
    var loadFailedSpy,
      loadError = new Error('mock error'),

      seq = sequencerWithDefaults(function(seqDefaultCfg) {

        loadFailedSpy = seqDefaultCfg.loadFailed;

        seqDefaultCfg.stateChanged.and.callFake(function(prevState, newState) {
          if (newState === sequencer.States.stopped) {
            runChecks();
          }
        });

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            slot.load.and.returnValue(Promise.reject(loadError));
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      _entries.forEach(function(entry, idx) {
        expect(loadFailedSpy.calls.argsFor(idx)).toEqual([entry, loadError]);
      });

      done();
    }
  });

  it('tries the next entry when a slot fails to load when skip was called', function(done) {
    var lastFailingEntryIdx = 2,
      startedSlot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            // make the all slots for the entries from 0 to 2 failing on load
            if (contains(_entries.slice(0, lastFailingEntryIdx + 1), slot.entry)) {
              slot.load.and.returnValue(Promise.reject());
            } else {
              slot.start.and.callFake(function() {
                startedSlot = slot;
                defer(runChecks);
              });
            }
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(startedSlot.entry).toEqual(_entries[lastFailingEntryIdx + 1]);
      done();
    }
  });

  it('tries the next entry when a slot fails to load when move (auto end) was called', function(done) {
    var lastFailingEntryIdx = 3,
      startedSlot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[0]) {
              slot.start.and.callFake(function() {
                defer(producerCfg.ending);
              });
            } else if (contains(_entries.slice(1, lastFailingEntryIdx + 1), slot.entry)) {
              // make the all slots for the entries from 1 to 3 failing on load
              slot.load.and.returnValue(Promise.reject());
            } else {
              slot.start.and.callFake(function() {
                startedSlot = slot;
                defer(runChecks);
              });
            }
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(startedSlot.entry).toEqual(_entries[lastFailingEntryIdx + 1]);
      done();
    }
  });

  it('triggers statesChanged with the right values when calling play / pause', function(done) {
    var stateChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedStateChangedCallsCount = 2,
          runChecksAfter2 = after(expectedStateChangedCallsCount, runChecks);

        stateChangedSpy = seqDefaultCfg.stateChanged.and.callFake(runChecksAfter2);
        return seqDefaultCfg;
      });

    seq.play();
    seq.pause();

    function runChecks() {
      var callsArgs = stateChangedSpy.calls.allArgs();
      expect(callsArgs[0]).toEqual([sequencer.States.pristine, sequencer.States.playing]);
      expect(callsArgs[1]).toEqual([sequencer.States.playing, sequencer.States.paused]);
      done();
    }
  });

  it('triggers statesChanged with "stopped" state when the last valid entry finished to play', function(done) {
    var stateChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedStateChangedCallsCount = 2,
          runChecksAfter2 = after(expectedStateChangedCallsCount, runChecks);

        stateChangedSpy = seqDefaultCfg.stateChanged;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[0]) {
              slot.start.and.callFake(function() {
                defer(producerCfg.ending);
              });
            } else {
              // make all the slots for the other entries failing on load
              slot.load.and.returnValue(Promise.reject());
            }
            return slot;
          },
          stateChanged: seqDefaultCfg.stateChanged.and.callFake(runChecksAfter2)
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(stateChangedSpy.calls.argsFor(1)).toEqual([sequencer.States.playing, sequencer.States.stopped]);
      done();
    }
  });

  it('calls loadingChanged on skip when en entry starts, stops and fails to load', function(done) {
    var loadingChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedLoadingChangedCallsCount = 4,
          runChecksAfter4 = after(expectedLoadingChangedCallsCount, runChecks);

        loadingChangedSpy = seqDefaultCfg.loadingChanged;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[1]) {
              slot.load.and.returnValue(Promise.reject());
            }
            return slot;
          },
          loadingChanged: seqDefaultCfg.loadingChanged.and.callFake(runChecksAfter4)
        };
      });

    seq.play();
    seq.skip(_entries[0]);
    defer(function() {
      seq.skip(_entries[1]);
    });

    function runChecks() {
      expect(loadingChangedSpy.calls.allArgs()).toEqual([
        [_entries[0], true],
        [_entries[0], false],
        [_entries[1], true],
        [_entries[1], false]
      ]);
      done();
    }
  });

});
<commit_msg>renamed a check function in sequencer unit test<commit_after>/* globals jasmine */

'use strict';

var sequencer = require('../../main/sequencer'),
  playbackSlotMock = require('./playbackSlotMock'),
  defer = require('./defer'),
  describe = jasmine.getEnv().describe,
  beforeEach = jasmine.getEnv().beforeEach,
  it = jasmine.getEnv().it,
  expect = jasmine.getEnv().expect,
  defaults = require('lodash/object/defaults'),
  after = require('lodash/function/after'),
  times = require('lodash/utility/times'),
  identity = require('lodash/utility/identity'),
  last = require('lodash/array/last'),
  initial = require('lodash/array/initial'),
  contains = require('lodash/collection/contains');

describe('A sequencer', function() {

  /**
   * @param {function(Object)=} inter
   * @returns {Sequencer}
   */
  function sequencerWithDefaults(inter) {
    inter = inter || identity;

    var defaultConfig = {
      nextEntryProducer: function(entry) {
        return null;
      },
      playbackSlotProducer: function(config) {
        var slot = playbackSlotMock({
          entry: config.entry,
          video: config.entry.video
        });
        slot.load.and.returnValue(Promise.resolve());
        slot.end.and.returnValue(Promise.resolve());
        return slot;
      },
      comingNext: jasmine.createSpy('comingNextSpy'),
      stateChanged: jasmine.createSpy('stateChangedSpy'),
      loadingChanged: jasmine.createSpy('loadingChangedSpy'),
      loadFailed: jasmine.createSpy('loadFailedSpy')
    };
    return sequencer(defaults({}, inter(defaultConfig), defaultConfig));
  }

  var _entries,
    _nextEntryProducer;

  beforeEach(function() {
    _entries = times(5, function(idx) {
      var id = 'mockEntry' + idx;
      return {
        id: id,
        video: {provider: 'mock', id: 'video-' + id}
      };
    });

    _nextEntryProducer = function(entry) {
      var idx = 0;
      if (entry) {
        idx = _entries.indexOf(entry) + 1;
      }
      if (idx >= _entries.length) {
        return null;
      }
      return _entries[idx];
    };
  });

  it('does not call nextEntryProducer first call to play', function(done) {
    var nextEntryProducerSpy = jasmine.createSpy('nextEntryProducerSpy');
    var seq = sequencerWithDefaults(function() {
      return {
        nextEntryProducer: nextEntryProducerSpy
      };
    });

    seq.play();

    defer(function() {
      expect(nextEntryProducerSpy).not.toHaveBeenCalledWith(null);

      done();
    });
  });

  it('executes the right sequence when manually skipping to an entry', function(done) {
    var nextEntryProducerSpy =
        jasmine.createSpy('nextEntryProducerSpy')
          .and.callFake(_nextEntryProducer),

      playbackSlotProducerSpy = jasmine.createSpy('nextEntryProducerSpy');

    var seq = sequencerWithDefaults(function(seqDefaultCfg) {
      return {
        nextEntryProducer: nextEntryProducerSpy,
        playbackSlotProducer: playbackSlotProducerSpy
          .and.callFake(function(producerCfg) {
            finishGate();
            return seqDefaultCfg.playbackSlotProducer(producerCfg);
          })
      };
    });

    var expectedSlotProducerCallsCount = 4,
      finishGate = after(expectedSlotProducerCallsCount, function() {

        expect(nextEntryProducerSpy.calls.allArgs()).toEqual([
          // asked for the next entry after the entry 1
          [_entries[0]],
          // asked for the next entry after the entry 3
          [_entries[3]]
        ]);

        var slotLoadedEntries = playbackSlotProducerSpy.calls.allArgs()
          .map(function(args) {
            return args[0].entry;
          });

        expect(slotLoadedEntries).toEqual([
          // play from pristine state
          _entries[0],
          // preload next after entry 1 playing
          _entries[1],
          // manually skipped to entry 3
          _entries[3],
          // preload next after entry 3 playing
          _entries[4]
        ]);

        defer(done);
      });

    seq.skip(_entries[0]);
    seq.play();

    defer(function() {
      seq.skip(_entries[3]);
    });
  });

  it('schedules the preloaded entry properly so that it gets played on automated ending', function(done) {

    var comingNextSpy,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {

        comingNextSpy = seqDefaultCfg.comingNext;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            // fake auto ending
            slot.start.and.callFake(function() {
              defer(function() {
                producerCfg.endingSoon();
                defer(function() {
                  producerCfg.ending();
                  finishGate();
                });
              });
            });

            return slot;
          }
        };
      });

    var finishGate = after(_entries.length, function() {

      expect(comingNextSpy.calls.count()).toEqual(_entries.length);

      done();
    });

    seq.play();
    seq.skip(_entries[0]);
  });

  it('keeps the correct entry to play after many consecutive skip calls', function(done) {

    var slots = [];

    var seq = sequencerWithDefaults(function(seqDefaultCfg) {
      return {
        nextEntryProducer: _nextEntryProducer,
        playbackSlotProducer: function(producerCfg) {
          var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
          slots.push(slot);
          return slot;
        }
      };
    });

    seq.play();

    // browses the list of entry and skip until the last one
    new Promise(function(resolve) {
      (function deferredWhile(idx) {
        if (idx < _entries.length) {
          defer(function() {
            seq.skip(_entries[idx]);
            deferredWhile(idx + 1);
          });
        } else {
          resolve();
        }
      })(0);
    }).then(function() {

        // all but the last slot (the one playing right now with no next entry) should have been ended

        initial(slots).forEach(function(slot) {
          expect(slot.end).toHaveBeenCalled();
        });

        expect(last(slots).end).not.toHaveBeenCalled();
        expect(last(slots).entry).toEqual(last(_entries));

        done();
      });
  });

  it('calls comingNext properly when skip is called', function(done) {
    var comingNextSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        comingNextSpy = seqDefaultCfg.comingNext.and.callFake(function() {
          defer(runChecks);
        });

        return {
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            slot.end.and.callFake(function() {
              defer(function() {
                producerCfg.endingSoon();
              });
            });
            return slot;
          }
        };
      });

    seq.skip(_entries[0]);
    seq.play();

    defer(function() {
      seq.skip(_entries[1]);
    });

    function runChecks() {
      expect(comingNextSpy).toHaveBeenCalledWith(_entries[0].video, _entries[1].video);

      done();
    }
  });

  it('pauses and resumes properly', function(done) {
    var slot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          playbackSlotProducer: function(producerCfg) {
            slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            slot.load.and.callFake(function() {
              defer(runChecks);
              return Promise.resolve();
            });

            return slot;
          }
        };
      });

    seq.skip(_entries[0]);

    // by default slots should be suspended until play is called
    expect(slot.suspend).toHaveBeenCalled();

    seq.play();

    function runChecks() {

      expect(slot.proceed).toHaveBeenCalled();

      done();
    }
  });

  it('it resumes properly if skip was called while paused', function(done) {
    var slots = [],
      slotsIdx = 0,
      steps = [step1, step2],
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);

            slots[slotsIdx] = slot;
            var step = steps[slotsIdx];

            slot.load.and.callFake(function() {
              defer(step);
              return Promise.resolve();
            });

            slotsIdx++;

            return slot;
          }
        };
      });

    seq.play();
    seq.pause();
    seq.skip(_entries[0]);

    function step1() {
      seq.skip(_entries[1]);
    }

    function step2() {
      seq.play();

      defer(function() {

        expect(slots[0].proceed).not.toHaveBeenCalled();
        expect(slots[1].proceed).toHaveBeenCalled();

        done();
      });
    }
  });

  it('calls the loadFailed callback when en entry failed to load', function(done) {
    var loadFailedSpy,
      loadError = new Error('mock error'),

      seq = sequencerWithDefaults(function(seqDefaultCfg) {

        loadFailedSpy = seqDefaultCfg.loadFailed;

        seqDefaultCfg.stateChanged.and.callFake(function(prevState, newState) {
          if (newState === sequencer.States.stopped) {
            runChecks();
          }
        });

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            slot.load.and.returnValue(Promise.reject(loadError));
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      _entries.forEach(function(entry, idx) {
        expect(loadFailedSpy.calls.argsFor(idx)).toEqual([entry, loadError]);
      });

      done();
    }
  });

  it('tries the next entry when a slot fails to load when skip was called', function(done) {
    var lastFailingEntryIdx = 2,
      startedSlot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            // make the all slots for the entries from 0 to 2 failing on load
            if (contains(_entries.slice(0, lastFailingEntryIdx + 1), slot.entry)) {
              slot.load.and.returnValue(Promise.reject());
            } else {
              slot.start.and.callFake(function() {
                startedSlot = slot;
                defer(runChecks);
              });
            }
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(startedSlot.entry).toEqual(_entries[lastFailingEntryIdx + 1]);
      done();
    }
  });

  it('tries the next entry when a slot fails to load when move (auto end) was called', function(done) {
    var lastFailingEntryIdx = 3,
      startedSlot,
      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[0]) {
              slot.start.and.callFake(function() {
                defer(producerCfg.ending);
              });
            } else if (contains(_entries.slice(1, lastFailingEntryIdx + 1), slot.entry)) {
              // make the all slots for the entries from 1 to 3 failing on load
              slot.load.and.returnValue(Promise.reject());
            } else {
              slot.start.and.callFake(function() {
                startedSlot = slot;
                defer(runChecks);
              });
            }
            return slot;
          }
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(startedSlot.entry).toEqual(_entries[lastFailingEntryIdx + 1]);
      done();
    }
  });

  it('triggers statesChanged with the right values when calling play / pause', function(done) {
    var stateChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedStateChangedCallsCount = 2,
          runChecksAfter2 = after(expectedStateChangedCallsCount, runChecks);

        stateChangedSpy = seqDefaultCfg.stateChanged.and.callFake(runChecksAfter2);
        return seqDefaultCfg;
      });

    seq.play();
    seq.pause();

    function runChecks() {
      var callsArgs = stateChangedSpy.calls.allArgs();
      expect(callsArgs[0]).toEqual([sequencer.States.pristine, sequencer.States.playing]);
      expect(callsArgs[1]).toEqual([sequencer.States.playing, sequencer.States.paused]);
      done();
    }
  });

  it('triggers statesChanged with "stopped" state when the last valid entry finished to play', function(done) {
    var stateChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedStateChangedCallsCount = 2,
          runChecksAfter2 = after(expectedStateChangedCallsCount, runChecks);

        stateChangedSpy = seqDefaultCfg.stateChanged;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[0]) {
              slot.start.and.callFake(function() {
                defer(producerCfg.ending);
              });
            } else {
              // make all the slots for the other entries failing on load
              slot.load.and.returnValue(Promise.reject());
            }
            return slot;
          },
          stateChanged: seqDefaultCfg.stateChanged.and.callFake(runChecksAfter2)
        };
      });

    seq.play();
    seq.skip(_entries[0]);

    function runChecks() {
      expect(stateChangedSpy.calls.argsFor(1)).toEqual([sequencer.States.playing, sequencer.States.stopped]);
      done();
    }
  });

  it('calls loadingChanged on skip when en entry starts, stops and fails to load', function(done) {
    var loadingChangedSpy,

      seq = sequencerWithDefaults(function(seqDefaultCfg) {
        var expectedLoadingChangedCallsCount = 4,
          runChecksAfter4 = after(expectedLoadingChangedCallsCount, runChecks);

        loadingChangedSpy = seqDefaultCfg.loadingChanged;

        return {
          nextEntryProducer: _nextEntryProducer,
          playbackSlotProducer: function(producerCfg) {
            var slot = seqDefaultCfg.playbackSlotProducer(producerCfg);
            if (slot.entry === _entries[1]) {
              slot.load.and.returnValue(Promise.reject());
            }
            return slot;
          },
          loadingChanged: seqDefaultCfg.loadingChanged.and.callFake(runChecksAfter4)
        };
      });

    seq.play();
    seq.skip(_entries[0]);
    defer(function() {
      seq.skip(_entries[1]);
    });

    function runChecks() {
      expect(loadingChangedSpy.calls.allArgs()).toEqual([
        [_entries[0], true],
        [_entries[0], false],
        [_entries[1], true],
        [_entries[1], false]
      ]);
      done();
    }
  });

});
<|endoftext|>


<commit_before>'use strict'

class Endpoint {

    constructor({
        name,
        allowHeaders = ['authorization'],
        allowMethods = ['GET']
    }) {
        this._name = name
        this._path = '/cloudfoundryapplication/' + (name ? name + '/' : '')
        this._allowHeaders = allowHeaders.join(', ')
        this._allowMethods = allowMethods.join(', ')
    }

    getName() {
        return this._name
    }

    getPath() {
        return this._path
    }

    handle(req, res) {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Headers', this._allowHeaders)
        res.setHeader('Access-Control-Allow-Methods', this._allowMethods)
    }
}

module.exports = Endpoint<commit_msg>docs: add jsdoc annotation<commit_after>'use strict'

/**
 *
 */
class Endpoint {

    constructor({
        name,
        allowHeaders = ['authorization'],
        allowMethods = ['GET']
    }) {
        this._name = name
        this._path = '/cloudfoundryapplication/' + (name ? name + '/' : '')
        this._allowHeaders = allowHeaders.join(', ')
        this._allowMethods = allowMethods.join(', ')
    }

    /**
     *
     * @returns {string} name of this endpoint
     */
    getName() {
        return this._name
    }

    /**
     *
     * @returns {string} registered path of this endpoint
     */
    getPath() {
        return this._path
    }

    /**
     *
     * @param {http.ClientRequest} req
     * @param {http.ClientResponse} res
     */
    handle(req, res) {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Headers', this._allowHeaders)
        res.setHeader('Access-Control-Allow-Methods', this._allowMethods)
    }
}

module.exports = Endpoint<|endoftext|>


<commit_before>import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

import createNewMeasurement from './bidirectionalTool/createNewMeasurement.js';
import pointNearTool from './bidirectionalTool/pointNearTool.js';
import renderToolData from './bidirectionalTool/renderToolData.js';
import addNewMeasurement from './bidirectionalTool/addNewMeasurement.js';
import _moveCallback from './bidirectionalTool/mouseMoveCallback.js';
import preMouseDownCallback from './bidirectionalTool/preMouseDownCallback.js';
import preTouchStartCallback from './bidirectionalTool/preTouchStartCallback.js';

const emptyLocationCallback = (measurementData, eventData, doneCallback) => doneCallback();

export default class BidirectionalTool extends BaseAnnotationTool {
  constructor (name = 'Bidirectional') {
    const textBoxConfig = '';
    const shadowConfig = '';

    super({
      name,
      supportedInteractionTypes: ['Mouse', 'Touch'],
      configuration: {
        changeMeasurementLocationCallback: emptyLocationCallback,
        textBox: textBoxConfig,
        shadow: shadowConfig,
        distanceThreshold: 28
      }
    });

    this.createNewMeasurement = createNewMeasurement.bind(this);
    this.pointNearTool = pointNearTool.bind(this);
    this.renderToolData = renderToolData.bind(this);
    this.addNewMeasurement = addNewMeasurement.bind(this);
    this._moveCallback = _moveCallback.bind(this);

    this.preMouseDownCallback = preMouseDownCallback.bind(this);
    this.preTouchStartCallback = preTouchStartCallback.bind(this);
  }

}

<commit_msg>fix(bidirectional-tool): Fix initial configuration for Bidirectional tool (#686)<commit_after>import BaseAnnotationTool from '../base/BaseAnnotationTool.js';

import createNewMeasurement from './bidirectionalTool/createNewMeasurement.js';
import pointNearTool from './bidirectionalTool/pointNearTool.js';
import renderToolData from './bidirectionalTool/renderToolData.js';
import addNewMeasurement from './bidirectionalTool/addNewMeasurement.js';
import _moveCallback from './bidirectionalTool/mouseMoveCallback.js';
import preMouseDownCallback from './bidirectionalTool/preMouseDownCallback.js';
import preTouchStartCallback from './bidirectionalTool/preTouchStartCallback.js';

const emptyLocationCallback = (measurementData, eventData, doneCallback) => doneCallback();

export default class BidirectionalTool extends BaseAnnotationTool {
  constructor (configuration = {}) {
    const textBoxConfig = '';
    const shadowConfig = '';

    const defaultConfig = {
      name: 'Bidirectional',
      supportedInteractionTypes: ['Mouse', 'Touch'],
      configuration: {
        changeMeasurementLocationCallback: emptyLocationCallback,
        textBox: textBoxConfig,
        shadow: shadowConfig,
        drawHandlesOnHover: true,
        distanceThreshold: 28
      }
    };

    const initialConfiguration = Object.assign(defaultConfig, configuration);

    super(initialConfiguration);

    this.createNewMeasurement = createNewMeasurement.bind(this);
    this.pointNearTool = pointNearTool.bind(this);
    this.renderToolData = renderToolData.bind(this);
    this.addNewMeasurement = addNewMeasurement.bind(this);
    this._moveCallback = _moveCallback.bind(this);

    this.preMouseDownCallback = preMouseDownCallback.bind(this);
    this.preTouchStartCallback = preTouchStartCallback.bind(this);
  }

}

<|endoftext|>


<commit_before>import _ from 'lodash';
import template from './template.html';

class AppCacheUpdaterCtrl {
    constructor($window, $timeout) {
        this.$window = $window;

        this.status = 'unsupported';
        this.progress = 0;

        if (!_.isUndefined($window.applicationCache)) {
            const cache = $window.applicationCache;

            // Show nothing for 'noupdate' (cache is fine) 'obsolete' (cache manifest gone) and 'cached' (initial cache)
            cache.addEventListener('noupdate', () => {
                this.status = 'idle';
            });
            cache.addEventListener('obsolete', () => {
                this.status = 'idle';
            });
            cache.addEventListener('cached', () => {
                this.status = 'idle';
            });

            // All other events set their own status
            ['checking', 'downloading', 'updateready', 'error', 'progress']
                .forEach(event => cache.addEventListener(event, () => {
                    this.status = event;
                }));

            // Listen to progress events to update the percentage
            cache.addEventListener('progress', progress => {
                this.progress = progress.loaded / progress.total * 100;
            });

            // Recheck cache every hour
            $timeout(() => cache.update(), 1000 * 60 * 60);
        }
    }

    shouldShow() {
        return this.status !== 'idle' && this.status !== 'unsupported';
    }

    reload() {
        this.$window.location.reload();
    }
}
AppCacheUpdaterCtrl.$inject = ['$window', '$timeout'];

/**
 * @ngdoc directive
 * @name directive:appCacheUpdater
 * @description Keeps track of appcache status and show download progress + offers to restart
 */
function appCacheUpdater() {
    return {
        restrict: 'EA',
        template,
        scope: {},
        controller: AppCacheUpdaterCtrl,
        controllerAs: 'updater',
        bindToController: true
    };
}
appCacheUpdater.$inject = [];

export default appCacheUpdater;
<commit_msg>fix issue where only the first update check would run<commit_after>import _ from 'lodash';
import template from './template.html';

class AppCacheUpdaterCtrl {
    constructor($window, $interval) {
        this.$window = $window;

        this.status = 'unsupported';
        this.progress = 0;

        if (!_.isUndefined($window.applicationCache)) {
            const cache = $window.applicationCache;

            // Show nothing for 'noupdate' (cache is fine) 'obsolete' (cache manifest gone) and 'cached' (initial cache)
            cache.addEventListener('noupdate', () => {
                this.status = 'idle';
            });
            cache.addEventListener('obsolete', () => {
                this.status = 'idle';
            });
            cache.addEventListener('cached', () => {
                this.status = 'idle';
            });

            // All other events set their own status
            ['checking', 'downloading', 'updateready', 'error', 'progress']
                .forEach(event => cache.addEventListener(event, () => {
                    this.status = event;
                }));

            // Listen to progress events to update the percentage
            cache.addEventListener('progress', progress => {
                this.progress = progress.loaded / progress.total * 100;
            });

            // Recheck cache every hour
            $interval(() => cache.update(), 1000 * 60 * 60);
        }
    }

    shouldShow() {
        return this.status !== 'idle' && this.status !== 'unsupported';
    }

    reload() {
        this.$window.location.reload();
    }
}
AppCacheUpdaterCtrl.$inject = ['$window', '$interval'];

/**
 * @ngdoc directive
 * @name directive:appCacheUpdater
 * @description Keeps track of appcache status and show download progress + offers to restart
 */
function appCacheUpdater() {
    return {
        restrict: 'EA',
        template,
        scope: {},
        controller: AppCacheUpdaterCtrl,
        controllerAs: 'updater',
        bindToController: true
    };
}
appCacheUpdater.$inject = [];

export default appCacheUpdater;
<|endoftext|>


<commit_before>/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var baseAttrs = require('../../plots/attributes');
var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
var colormodel = require('./constants').colormodel;

var cm = ['rgb', 'rgba', 'rgba256', 'hsl', 'hsla'];
var zminDesc = [];
var zmaxDesc = [];
for(var i = 0; i < cm.length; i++) {
    var cr = colormodel[cm[i]];
    zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zminDflt || cr.min).join(', ') + '].');
    zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zmaxDflt || cr.max).join(', ') + '].');
}

module.exports = extendFlat({
    source: {
        valType: 'string',
        role: 'info',
        editType: 'calc',
        description: [
            'Specifies the data URI of the image to be visualized.',
            'The URI consists of "data:image/[<media subtype>][;base64],<data>"'
        ].join(' ')
    },
    z: {
        valType: 'data_array',
        role: 'info',
        editType: 'calc',
        description: [
            'A 2-dimensional array in which each element is an array of 3 or 4 numbers representing a color.',
        ].join(' ')
    },
    colormodel: {
        valType: 'enumerated',
        values: cm,
        role: 'info',
        editType: 'calc',
        description: [
            'Color model used to map the numerical color components described in `z` into colors.',
            'If `source` is specified, this attribute will be set to `rgba256`',
            'otherwise it defaults to `rgb`.'
        ].join(' ')
    },
    interpolate: {
        valType: 'enumerated',
        values: ['nearest', 'linear'],
        dflt: 'nearest',
        role: 'info',
        editType: 'plot',
        description: [
            'The type of interpolation for rendering the image.',
            'Default `nearest` (nearest neighbor interpolation).',
            'Can also be set to `linear` (bilinear interpolation).'
        ].join(' ')
    },
    zmin: {
        valType: 'info_array',
        items: [
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'}
        ],
        role: 'info',
        editType: 'calc',
        description: [
            'Array defining the lower bound for each color component.',
            'Note that the default value will depend on the colormodel.',
            zminDesc.join(' ')
        ].join(' ')
    },
    zmax: {
        valType: 'info_array',
        items: [
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'}
        ],
        role: 'info',
        editType: 'calc',
        description: [
            'Array defining the higher bound for each color component.',
            'Note that the default value will depend on the colormodel.',
            zmaxDesc.join(' ')
        ].join(' ')
    },
    x0: {
        valType: 'any',
        dflt: 0,
        role: 'info',
        editType: 'calc+clearAxisTypes',
        description: 'Set the image\'s x position.'
    },
    y0: {
        valType: 'any',
        dflt: 0,
        role: 'info',
        editType: 'calc+clearAxisTypes',
        description: 'Set the image\'s y position.'
    },
    dx: {
        valType: 'number',
        dflt: 1,
        role: 'info',
        editType: 'calc',
        description: 'Set the pixel\'s horizontal size.'
    },
    dy: {
        valType: 'number',
        dflt: 1,
        role: 'info',
        editType: 'calc',
        description: 'Set the pixel\'s vertical size'
    },
    text: {
        valType: 'data_array',
        editType: 'plot',
        description: 'Sets the text elements associated with each z value.'
    },
    hovertext: {
        valType: 'data_array',
        editType: 'plot',
        description: 'Same as `text`.'
    },
    hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
        flags: ['x', 'y', 'z', 'color', 'name', 'text'],
        dflt: 'x+y+z+text+name'
    }),
    hovertemplate: hovertemplateAttrs({}, {
        keys: ['z', 'color', 'colormodel']
    }),

    transforms: undefined
});
<commit_msg>Update src/traces/image/attributes.js<commit_after>/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var baseAttrs = require('../../plots/attributes');
var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = require('../../lib/extend').extendFlat;
var colormodel = require('./constants').colormodel;

var cm = ['rgb', 'rgba', 'rgba256', 'hsl', 'hsla'];
var zminDesc = [];
var zmaxDesc = [];
for(var i = 0; i < cm.length; i++) {
    var cr = colormodel[cm[i]];
    zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zminDflt || cr.min).join(', ') + '].');
    zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zmaxDflt || cr.max).join(', ') + '].');
}

module.exports = extendFlat({
    source: {
        valType: 'string',
        role: 'info',
        editType: 'calc',
        description: [
            'Specifies the data URI of the image to be visualized.',
            'The URI consists of "data:image/[<media subtype>][;base64],<data>"'
        ].join(' ')
    },
    z: {
        valType: 'data_array',
        role: 'info',
        editType: 'calc',
        description: [
            'A 2-dimensional array in which each element is an array of 3 or 4 numbers representing a color.',
        ].join(' ')
    },
    colormodel: {
        valType: 'enumerated',
        values: cm,
        role: 'info',
        editType: 'calc',
        description: [
            'Color model used to map the numerical color components described in `z` into colors.',
            'If `source` is specified, this attribute will be set to `rgba256`',
            'otherwise it defaults to `rgb`.'
        ].join(' ')
    },
    zsmooth: {
        valType: 'enumerated',
        values: ['nearest', 'linear'],
        dflt: 'nearest',
        role: 'info',
        editType: 'plot',
        description: [
            'The type of interpolation for rendering the image.',
            'Default `nearest` (nearest neighbor interpolation).',
            'Can also be set to `linear` (bilinear interpolation).'
        ].join(' ')
    },
    zmin: {
        valType: 'info_array',
        items: [
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'}
        ],
        role: 'info',
        editType: 'calc',
        description: [
            'Array defining the lower bound for each color component.',
            'Note that the default value will depend on the colormodel.',
            zminDesc.join(' ')
        ].join(' ')
    },
    zmax: {
        valType: 'info_array',
        items: [
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'},
            {valType: 'number', editType: 'calc'}
        ],
        role: 'info',
        editType: 'calc',
        description: [
            'Array defining the higher bound for each color component.',
            'Note that the default value will depend on the colormodel.',
            zmaxDesc.join(' ')
        ].join(' ')
    },
    x0: {
        valType: 'any',
        dflt: 0,
        role: 'info',
        editType: 'calc+clearAxisTypes',
        description: 'Set the image\'s x position.'
    },
    y0: {
        valType: 'any',
        dflt: 0,
        role: 'info',
        editType: 'calc+clearAxisTypes',
        description: 'Set the image\'s y position.'
    },
    dx: {
        valType: 'number',
        dflt: 1,
        role: 'info',
        editType: 'calc',
        description: 'Set the pixel\'s horizontal size.'
    },
    dy: {
        valType: 'number',
        dflt: 1,
        role: 'info',
        editType: 'calc',
        description: 'Set the pixel\'s vertical size'
    },
    text: {
        valType: 'data_array',
        editType: 'plot',
        description: 'Sets the text elements associated with each z value.'
    },
    hovertext: {
        valType: 'data_array',
        editType: 'plot',
        description: 'Same as `text`.'
    },
    hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
        flags: ['x', 'y', 'z', 'color', 'name', 'text'],
        dflt: 'x+y+z+text+name'
    }),
    hovertemplate: hovertemplateAttrs({}, {
        keys: ['z', 'color', 'colormodel']
    }),

    transforms: undefined
});
<|endoftext|>