<commit_before>/*
 * Copyright (c) 2014, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
'use strict';
var request = require('request');
var parseXml = require('xml2js').parseString;
var _ = require('lodash');

function buildDownloadUrl(version, minorVersion) {
  return 'https://selenium-release.storage.googleapis.com/' + minorVersion +
    '/selenium-server-standalone-' + version + '.jar';
}

var FALLBACK_SELENIUM_VERSION = '2.53.0';
var FORCE_SELENIUM_VERSION = {
  downloadUrl: buildDownloadUrl('2.53.0', '2.53'),
  version: '2.53.0'
};

function parseSeleniumMinor(result) {
  try {
    var prefixes = result.ListBucketResult.CommonPrefixes;
    var prefix = prefixes[prefixes.length - 2];
    var versionPath = prefix.Prefix[0];
    return {
      error: null,
      minorVersion: versionPath.substring(0, versionPath.length - 1)
    };
  } catch (parseError) {
    return { error: parseError, minorVersion: null };
  }
}

function parseSelenium(result) {
  try {
    var contents = result.ListBucketResult.Contents;
    var standalone = _.find(contents, function matchContent(content) {
      return content.Key[0].match(/selenium-server-standalone-/);
    });
    return {
      error: null,
      version: standalone.Key[0].match(/(\d+\.\d+\.\d+)/)[0]
    };
  } catch (parseError) {
    return { error: parseError, version: null };
  }
}

function requestXml(url, callback) {
  function onParsedXml(error, result) {
    if (error != null) {
      return callback(error);
    }
    return callback(null, result);
  }

  return request(url, function onResponse(error, response, body) {
    if (error != null) {
      return callback(error);
    }
    return parseXml(body, onParsedXml);
  });
}

function getMinor(version) {
  return version.split('.').slice(0, 2).join('.');
}

function getLatestVersion(callback) {
  var url = 'https://selenium-release.storage.googleapis.com/?delimiter=/&prefix=';

  function onFullVersionData(error, result) {
    if (error != null) {
      return callback(error);
    }
    var parsed = parseSelenium(result);
    return callback(parsed.error, parsed.version);
  }

  function onMinorVersionData(error, result) {
    if (error != null) {
      return callback(error);
    }
    var parsedMinor = parseSeleniumMinor(result);
    if (parsedMinor.error) {
      return callback(parsedMinor.error);
    }
    var prefixedUrl =
      'https://selenium-release.storage.googleapis.com/?delimiter=/&prefix=' +
      parsedMinor.minorVersion + '/';
    return requestXml(prefixedUrl, onFullVersionData);
  }

  return requestXml(url, onMinorVersionData);
}

function getLatestDownloadInfo(callback) {
  if (FORCE_SELENIUM_VERSION != null) {
    return callback(null, FORCE_SELENIUM_VERSION);
  }
  return getLatestVersion(function onVersion(error, version) {
    if (error != null) {
      version = FALLBACK_SELENIUM_VERSION;
      /* eslint no-console: 0 */
      console.log(
        '[testium] Unable to determine latest version of selenium standalone server; using %s',
        version);
      console.error(error.stack || error);
    }
    var minorVersion = getMinor(version);
    var downloadUrl = buildDownloadUrl(version, minorVersion);
    return callback(null, {
      downloadUrl: downloadUrl,
      version: version
    });
  });
}
module.exports = getLatestDownloadInfo;
<commit_msg>fix: The XML source from where we were getting the version changed<commit_after>/*
 * Copyright (c) 2014, Groupon, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of GROUPON nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
'use strict';
var request = require('request');
var parseXml = require('xml2js').parseString;
var _ = require('lodash');

function buildDownloadUrl(version, minorVersion) {
  return 'https://selenium-release.storage.googleapis.com/' + minorVersion +
    '/selenium-server-standalone-' + version + '.jar';
}

var FALLBACK_SELENIUM_VERSION = '2.53.0';

function parseSeleniumMinor(result) {
  try {
    var prefixes = result.ListBucketResult.CommonPrefixes;
    var prefix = prefixes[prefixes.length - 3];
    var versionPath = prefix.Prefix[0];
    return {
      error: null,
      minorVersion: versionPath.substring(0, versionPath.length - 1)
    };
  } catch (parseError) {
    return { error: parseError, minorVersion: null };
  }
}

function parseSelenium(result) {
  try {
    var contents = result.ListBucketResult.Contents;
    var standalone = _.find(contents, function matchContent(content) {
      return content.Key[0].match(/selenium-server-standalone-/);
    });
    return {
      error: null,
      version: standalone.Key[0].match(/(\d+\.\d+\.\d+)/)[0]
    };
  } catch (parseError) {
    return { error: parseError, version: null };
  }
}

function requestXml(url, callback) {
  function onParsedXml(error, result) {
    if (error != null) {
      return callback(error);
    }
    return callback(null, result);
  }

  return request(url, function onResponse(error, response, body) {
    if (error != null) {
      return callback(error);
    }
    return parseXml(body, onParsedXml);
  });
}

function getMinor(version) {
  return version.split('.').slice(0, 2).join('.');
}

function getLatestVersion(callback) {
  var url = 'https://selenium-release.storage.googleapis.com/?delimiter=/&prefix=';

  function onFullVersionData(error, result) {
    if (error != null) {
      return callback(error);
    }
    var parsed = parseSelenium(result);
    return callback(parsed.error, parsed.version);
  }

  function onMinorVersionData(error, result) {
    if (error != null) {
      return callback(error);
    }
    var parsedMinor = parseSeleniumMinor(result);
    if (parsedMinor.error) {
      return callback(parsedMinor.error);
    }
    var prefixedUrl =
      'https://selenium-release.storage.googleapis.com/?delimiter=/&prefix=' +
      parsedMinor.minorVersion + '/';
    return requestXml(prefixedUrl, onFullVersionData);
  }

  return requestXml(url, onMinorVersionData);
}

function getLatestDownloadInfo(callback) {
  return getLatestVersion(function onVersion(error, version) {
    if (error != null) {
      version = FALLBACK_SELENIUM_VERSION;
      /* eslint no-console: 0 */
      console.log(
        '[testium] Unable to determine latest version of selenium standalone server; using %s',
        version);
      console.error(error.stack || error);
    }
    var minorVersion = getMinor(version);
    var downloadUrl = buildDownloadUrl(version, minorVersion);
    return callback(null, {
      downloadUrl: downloadUrl,
      version: version
    });
  });
}
module.exports = getLatestDownloadInfo;
<|endoftext|>


<commit_before>'use strict'

// ----------------------------------------------------------------------------

const log = require ('ololog')
    , testTicker = require ('ccxt/js/test/Exchange/test.ticker.js')

/*  ------------------------------------------------------------------------ */

module.exports = async (exchange, symbol) => {

    // log (symbol.green, 'watching ticker...')

    const method = 'watchTicker'

    if (!exchange.has[method]) {
        log (exchange.id, 'does not support', method + '() method')
        return
    }

    let response = undefined

    for (let i = 0; i < 5; i++) {

        response = await exchange[method] (symbol)
        testTicker (exchange, response, method, symbol)
    }

    return response
}<commit_msg>test.watchTicker.js<commit_after>'use strict'

// ----------------------------------------------------------------------------

const log = require ('ololog')
    , testTicker = require ('ccxt/js/test/Exchange/test.ticker.js')

/*  ------------------------------------------------------------------------ */

module.exports = async (exchange, symbol) => {

    // log (symbol.green, 'watching ticker...')

    const method = 'watchTicker'

    if (!exchange.has[method]) {
        log (exchange.id, method, 'is not supported or not implemented yet')
        return
    }

    let response = undefined

    for (let i = 0; i < 3; i++) {

        response = await exchange[method] (symbol)
        testTicker (exchange, response, method, symbol)
    }

    return response
}<|endoftext|>


<commit_before>/**
 * Sync() module for creating and managing syncs
 * between client and server
 */
var filesystem = require( "./filesystem" ),
    SyncMessage = require('../../lib/syncmessage'),
    rsync = require('../../lib/rsync'),
    diffHelper = require('../../lib/diff'),
    rsyncOptions = require('../../lib/constants').rsyncDefaults;

/**
 * Static public variables
 */
Sync.LISTENING = "LISTENING";
Sync.OUT_OF_DATE = "OUT OF DATE";
Sync.CHKSUM = "CHECKSUM";
Sync.PATCH = "PATCH";

/**
 * Static private variables
 */
var connectedClients = {};

/**
 * Helper functions
 */
// Handle requested resources
function handleRequest(data) {
  var that = this;
  var response;

  function sendSrcList() {
    rsync.sourceList(that.fs, '/', rsyncOptions, function(err, srcList) {
      if(err) {
        response = SyncMessage.error.srclist;
        response.content = {error: err};
      } else {
        response = SyncMessage.request.chksum;
        response.content = {srcList: srcList, path: '/'};
      }
      that.socket.send(response.stringify());
    });
  }

  function resetUpstream() {
    that.end();
    that.state = Sync.LISTENING;
  }

  function handleDiffRequest() {
    if(!data.content.checksums) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var checksums = data.content.checksums;

    rsync.diff(that.fs, that.path, checksums, rsyncOptions, function(err, diffs) {
      if(err) {
        response = SyncMessage.error.diffs;
        response.content = {error: err};
      } else {
        response = SyncMessage.response.diffs;
        response.content = {
          diffs: diffHelper.serialize(diffs),
          path: that.path
        };
      }
      that.socket.send(response.stringify());
    });
  }

  function handleSyncInitRequest() {
    if(that.canSync()) {
      response = SyncMessage.response.sync;
      that.state = Sync.CHKSUM;
      that.init();
    } else {
      response = SyncMessage.error.locked;
    }
    that.socket.send(response.stringify());
  }

  function handleChecksumRequest() {
    if(!data.content.srcList || !data.content.path) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var srcList = data.content.srcList;
    var path = data.content.path;

    rsync.checksums(that.fs, path, srcList, rsyncOptions, function(err, checksums) {
      if(err) {
        that.state = Sync.LISTENING;
        that.end();
        response = SyncMessage.error.chksum;
        response.content = {error: err};
      } else {
        response = SyncMessage.request.diffs;
        response.content = {checksums: checksums, path: that.path};
        that.state = Sync.PATCH;
      }
      that.socket.send(response.stringify());
    });
  }

  if(data.is.reset && that.state === Sync.OUT_OF_DATE) {
    sendSrcList();
  } else if(data.is.reset && that.state !== Sync.OUT_OF_DATE) {
    resetUpstream();
  } else if(data.is.diffs && that.state === Sync.OUT_OF_DATE) {
    handleDiffRequest();
  } else if(data.is.sync && that.state === Sync.LISTENING) {
    handleSyncInitRequest();
  } else if(data.is.chksum && that.state === Sync.CHKSUM) {
    handleChecksumRequest();
  } else {
    that.socket.send(Sync.error.request.stringify());
  }
}

// Handle responses sent by the client
function handleResponse(data) {
  var that = this;
  var response;

  function handleDiffResponse() {
    if(!data.content.diffs) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var diffs = diffHelper.deserialize(data.content.diffs);
    var path = data.content.path;
    that.state = Sync.LISTENING;
    rsync.patch(that.fs, path, diffs, rsyncOptions, function(err) {
      if(err) {
        that.end();
        return SyncMessage.error.patch;
      }

      that.socket.send(SyncMessage.response.patch.stringify());
      that.end();
    });
  }

  function handlePatchResponse() {
    // TODO: Figure out how to make sure that the client actually patched successfully
    // before changing the server's state to allow upstream syncs from that client
    // https://github.com/mozilla/makedrive/issues/32
    that.state = Sync.LISTENING;
  }

  if(data.is.diffs && that.state === Sync.PATCH) {
    handleDiffResponse();
  } else if(data.is.patch && that.state === Sync.OUT_OF_DATE) {
    handlePatchResponse();
  } else {
    that.socket.send(Sync.error.response.stringify());
  }
}

// Broadcast an out-of-date message to the clients
// after an upstream sync process has completed
function broadcastUpdate(username) {
  var clients = connectedClients[username];
  var currSyncingClient = clients.currentSyncSession;
  var updateMsg = SyncMessage.request.chksum.stringify();
  var outOfDateClient;
  if(clients) {
    for(var sessionId in clients) {
      if(currSyncingClient !== sessionId) {
        outOfDateClient = clients[sessionId].sync;
        outOfDateClient.state = Sync.OUT_OF_DATE;
        outOfDateClient.socket.send(updateMsg);
      }
    }
  }
}

/**
 * Constructor
 */
function Sync(username, sessionId) {
  this.sessionId = sessionId;
  this.username = username;

  // Ensure the current user exists in our datastore and
  // track this client session keyed by a new `syncId`
  if (!connectedClients[username]) {
    connectedClients[username] = {};
  }
  connectedClients[username][sessionId] = {
    sync: this
  };
  this.fs = filesystem.create({
    keyPrefix: username,
    name: username
  });
  this.state = Sync.OUT_OF_DATE;
  this.path = '/';
}

// Initialize a sync for a client
Sync.prototype.init = function() {
  if(!(connectedClients[this.username].currentSyncSession)) {
    connectedClients[this.username].currentSyncSession = this.sessionId;
  }
};

Sync.prototype.onClose = function( ) {
  var username = this.username;
  var id = this.syncId;

  return function() {
    emitter.removeListener( "updateToLatestSync", connectedClients[ username ][ id ].onOutOfDate );
    delete connectedClients[ username ][ id ];

    // Also remove the username from the list if there are no more connected clients.
    if( Object.keys( connectedClients[ username ] ).count === 0 ) {
      delete connectedClients[ username ];
    }
  };
};

// Terminate a sync for a client
Sync.prototype.end = function() {
  if(connectedClients[this.username].currentSyncSession) {
    broadcastUpdate(this.username);
    delete connectedClients[this.username].currentSyncSession;
  }
};

// Can the client begin an upstream sync if
// there is no other sync in progress for that username
Sync.prototype.canSync = function() {
  return connectedClients[this.username] &&
    !(connectedClients[this.username].currentSyncSession);
};

// Handle a message sent by the client
Sync.prototype.messageHandler = function(data) {
  data = SyncMessage.parse(data);

  if(data.is.request) {
    handleRequest.call(this, data);
  } else if(data.is.response) {
    handleResponse.call(this, data);
  } else {
    this.socket.send(Sync.error.type.stringify());
  }
};

// Store the socket for the current client
Sync.prototype.setSocket = function(ws) {
  this.socket = ws;
};

// Close event for a sync
Sync.prototype.onClose = function() {
  var username = this.username;
  var id = this.syncId;

  return function() {
    delete connectedClients[username][id];

    // Also remove the username from the list if there are no more connected clients.
    if(Object.keys(connectedClients[username]).count === 0) {
      delete connectedClients[username];
    }
  };
};

/**
 * Public static objects/methods
 */
Sync.error = {
  get type() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'The Sync message cannot be handled by the server'};
    return message;
  },
  get request() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'Request cannot be processed'};
    return message;
  },
  get response() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'The resource sent as a response cannot be processed'};
    return message;
  }
});

// Create a new sync object for the client
Sync.create = function(username, sessionId){
  return new Sync(username, sessionId);
};

/**
 * Exports
 */
module.exports = Sync;
<commit_msg>Tiny fix<commit_after>/**
 * Sync() module for creating and managing syncs
 * between client and server
 */
var filesystem = require( "./filesystem" ),
    SyncMessage = require('../../lib/syncmessage'),
    rsync = require('../../lib/rsync'),
    diffHelper = require('../../lib/diff'),
    rsyncOptions = require('../../lib/constants').rsyncDefaults;

/**
 * Static public variables
 */
Sync.LISTENING = "LISTENING";
Sync.OUT_OF_DATE = "OUT OF DATE";
Sync.CHKSUM = "CHECKSUM";
Sync.PATCH = "PATCH";

/**
 * Static private variables
 */
var connectedClients = {};

/**
 * Helper functions
 */
// Handle requested resources
function handleRequest(data) {
  var that = this;
  var response;

  function sendSrcList() {
    rsync.sourceList(that.fs, '/', rsyncOptions, function(err, srcList) {
      if(err) {
        response = SyncMessage.error.srclist;
        response.content = {error: err};
      } else {
        response = SyncMessage.request.chksum;
        response.content = {srcList: srcList, path: '/'};
      }
      that.socket.send(response.stringify());
    });
  }

  function resetUpstream() {
    that.end();
    that.state = Sync.LISTENING;
  }

  function handleDiffRequest() {
    if(!data.content.checksums) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var checksums = data.content.checksums;

    rsync.diff(that.fs, that.path, checksums, rsyncOptions, function(err, diffs) {
      if(err) {
        response = SyncMessage.error.diffs;
        response.content = {error: err};
      } else {
        response = SyncMessage.response.diffs;
        response.content = {
          diffs: diffHelper.serialize(diffs),
          path: that.path
        };
      }
      that.socket.send(response.stringify());
    });
  }

  function handleSyncInitRequest() {
    if(that.canSync()) {
      response = SyncMessage.response.sync;
      that.state = Sync.CHKSUM;
      that.init();
    } else {
      response = SyncMessage.error.locked;
    }
    that.socket.send(response.stringify());
  }

  function handleChecksumRequest() {
    if(!data.content.srcList || !data.content.path) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var srcList = data.content.srcList;
    var path = data.content.path;

    rsync.checksums(that.fs, path, srcList, rsyncOptions, function(err, checksums) {
      if(err) {
        that.state = Sync.LISTENING;
        that.end();
        response = SyncMessage.error.chksum;
        response.content = {error: err};
      } else {
        response = SyncMessage.request.diffs;
        response.content = {checksums: checksums, path: that.path};
        that.state = Sync.PATCH;
      }
      that.socket.send(response.stringify());
    });
  }

  if(data.is.reset && that.state === Sync.OUT_OF_DATE) {
    sendSrcList();
  } else if(data.is.reset && that.state !== Sync.OUT_OF_DATE) {
    resetUpstream();
  } else if(data.is.diffs && that.state === Sync.OUT_OF_DATE) {
    handleDiffRequest();
  } else if(data.is.sync && that.state === Sync.LISTENING) {
    handleSyncInitRequest();
  } else if(data.is.chksum && that.state === Sync.CHKSUM) {
    handleChecksumRequest();
  } else {
    that.socket.send(Sync.error.request.stringify());
  }
}

// Handle responses sent by the client
function handleResponse(data) {
  var that = this;
  var response;

  function handleDiffResponse() {
    if(!data.content.diffs) {
      return that.socket.send(SyncMessage.error.content.stringify());
    }

    var diffs = diffHelper.deserialize(data.content.diffs);
    var path = data.content.path;
    that.state = Sync.LISTENING;
    rsync.patch(that.fs, path, diffs, rsyncOptions, function(err) {
      if(err) {
        that.end();
        return SyncMessage.error.patch;
      }

      that.socket.send(SyncMessage.response.patch.stringify());
      that.end();
    });
  }

  function handlePatchResponse() {
    // TODO: Figure out how to make sure that the client actually patched successfully
    // before changing the server's state to allow upstream syncs from that client
    // https://github.com/mozilla/makedrive/issues/32
    that.state = Sync.LISTENING;
  }

  if(data.is.diffs && that.state === Sync.PATCH) {
    handleDiffResponse();
  } else if(data.is.patch && that.state === Sync.OUT_OF_DATE) {
    handlePatchResponse();
  } else {
    that.socket.send(Sync.error.response.stringify());
  }
}

// Broadcast an out-of-date message to the clients
// after an upstream sync process has completed
function broadcastUpdate(username) {
  var clients = connectedClients[username];
  var currSyncingClient = clients.currentSyncSession;
  var updateMsg = SyncMessage.request.chksum.stringify();
  var outOfDateClient;
  if(clients) {
    for(var sessionId in clients) {
      if(currSyncingClient !== sessionId) {
        outOfDateClient = clients[sessionId].sync;
        outOfDateClient.state = Sync.OUT_OF_DATE;
        outOfDateClient.socket.send(updateMsg);
      }
    }
  }
}

/**
 * Constructor
 */
function Sync(username, sessionId) {
  this.sessionId = sessionId;
  this.username = username;

  // Ensure the current user exists in our datastore and
  // track this client session keyed by a new `syncId`
  if (!connectedClients[username]) {
    connectedClients[username] = {};
  }
  connectedClients[username][sessionId] = {
    sync: this
  };
  this.fs = filesystem.create({
    keyPrefix: username,
    name: username
  });
  this.state = Sync.OUT_OF_DATE;
  this.path = '/';
}

// Initialize a sync for a client
Sync.prototype.init = function() {
  if(!(connectedClients[this.username].currentSyncSession)) {
    connectedClients[this.username].currentSyncSession = this.sessionId;
  }
};

Sync.prototype.onClose = function( ) {
  var username = this.username;
  var id = this.syncId;

  return function() {
    emitter.removeListener( "updateToLatestSync", connectedClients[ username ][ id ].onOutOfDate );
    delete connectedClients[ username ][ id ];

    // Also remove the username from the list if there are no more connected clients.
    if( Object.keys( connectedClients[ username ] ).count === 0 ) {
      delete connectedClients[ username ];
    }
  };
};

// Terminate a sync for a client
Sync.prototype.end = function() {
  if(connectedClients[this.username].currentSyncSession) {
    broadcastUpdate(this.username);
    delete connectedClients[this.username].currentSyncSession;
  }
};

// Can the client begin an upstream sync if
// there is no other sync in progress for that username
Sync.prototype.canSync = function() {
  return connectedClients[this.username] &&
    !(connectedClients[this.username].currentSyncSession);
};

// Handle a message sent by the client
Sync.prototype.messageHandler = function(data) {
  data = SyncMessage.parse(data);

  if(data.is.request) {
    handleRequest.call(this, data);
  } else if(data.is.response) {
    handleResponse.call(this, data);
  } else {
    this.socket.send(Sync.error.type.stringify());
  }
};

// Store the socket for the current client
Sync.prototype.setSocket = function(ws) {
  this.socket = ws;
};

// Close event for a sync
Sync.prototype.onClose = function() {
  var username = this.username;
  var id = this.syncId;

  return function() {
    delete connectedClients[username][id];

    // Also remove the username from the list if there are no more connected clients.
    if(Object.keys(connectedClients[username]).count === 0) {
      delete connectedClients[username];
    }
  };
};

/**
 * Public static objects/methods
 */
Sync.error = {
  get type() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'The Sync message cannot be handled by the server'};
    return message;
  },
  get request() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'Request cannot be processed'};
    return message;
  },
  get response() {
    var message = SyncMessage.error.impl;
    message.content = {error: 'The resource sent as a response cannot be processed'};
    return message;
  }
};

// Create a new sync object for the client
Sync.create = function(username, sessionId){
  return new Sync(username, sessionId);
};

/**
 * Exports
 */
module.exports = Sync;
<|endoftext|>


<commit_before>Ext.ns('Garp.dataTypes');
Garp.dataTypes.Image.on('init', function(){

	this.iconCls = 'icon-img';

	// Thumbnail column:
	this.insertColumn(0, {
		header: '' + __('Image') + '',
		dataIndex: 'id',
		width: 84,
		fixed: true,
		renderer: Garp.renderers.imageRelationRenderer,
		hidden: false
	});

	this.addListener('loaddata', function(rec, formPanel){
		function updateUI(){
			formPanel.preview.update(Garp.renderers.imagePreviewRenderer(rec.get('filename'), null, rec));
			formPanel.download.update({
				filename: rec.get('filename')
			});
		}
		if (formPanel.rendered) {
			updateUI();
		} else {
			formPanel.on('show', updateUI, null, {
				single: true
			});
		}
		// if we're in a relateCreateWindow, set it's height again, otherwise it might not fit.
		if (typeof formPanel.center == 'function' && rec.get('filename')) {
			formPanel.setHeight(440);
			formPanel.center();
		}
	}, true);

	// Remove these fields, cause we are about to change the order and appearance of them...
	this.removeField('caption');
	this.removeField('filename');
	this.removeField('id');

	var fDesc = this.getField('filename_info');
	if (fDesc) {
		this.removeField('filename_info');
	} else {
		fDesc = {
			xtype: 'box',
			cls: 'garp-notification-boxcomponent',
			name: 'filename_info',
			html: __('Only {1} and {2} files with a maximum of {3} MB are accepted', 'jpg, png', 'gif', '20'),
			fieldLabel: ' '
		};
	}

	// ...include them again:
	this.insertField(0, {
		xtype: 'fieldset',
		style: 'margin: 0;padding:0;',
		items: [{
			name: 'id',
			hideFieldLabel: true,
			disabled: true,
			xtype: 'numberfield',
			hidden: true,
			ref: '../../../../_id'
		}, {
			name: 'filename',
			fieldLabel: __('Filename'),
			xtype: 'uploadfield',
			allowBlank: false,
			emptyText: __('Drag image here, or click browse button'),
			uploadURL: BASE + 'g/content/upload/type/image',
			ref: '../../../../filename',

			listeners: {
				'change': function(field, val){
					if (this.refOwner._id.getValue()) {
						var url = BASE + 'admin?' +
						Ext.urlEncode({
							model: Garp.currentModel,
							id: this.refOwner._id.getValue()
						});
						if (DEBUG) {
							url += '#DEBUG';
						}

						// because images won't reload if their ID is
						// still the same, we need to reload the page
						this.refOwner.formcontent.on('loaddata', function(){
							var lm = new Ext.LoadMask(Ext.getBody());
							lm.show();
							document.location.href = url;
						});
						this.refOwner.fireEvent('save-all');
					} else {
						field.originalValue = ''; // mark dirty
						this.refOwner.preview.update(Garp.renderers.uploadedImagePreviewRenderer(val));
						this.refOwner.download.update({
							filename: val
						});
						this.refOwner.fireEvent('save-all');
					}
					return true;
				}
			}
		}, fDesc, {
			name: 'caption',
			xtype: 'textfield',
			fieldLabel: __('Caption')
		}, {
			xtype: 'box',
			ref: '../../../../preview',
			fieldLabel: __('Preview'),
			cls: 'preview',
			html: ''
		}, {
			xtype: 'box',
			hidden: false,
			ref: '../../../../download',
			fieldLabel: ' ',
			hideFieldLabel: false,
			tpl: new Ext.XTemplate('<tpl if="filename">', '' + __('View original file') + '', '</tpl>')
		}]
	});

	// Wysiwyg Editor
	this.Wysiwyg = Ext.extend(Garp.WysiwygAbstract, {

		model: 'Image',

		idProperty: 'id',

		settingsMenu: true,

		margin: 0,

		getData: function(){
			return {
				id: this._data.id,
				caption: this._data.caption
			};
		},

		// override: we don't need filtering for images:
		filterHtml: function(){
			return true;
		},

		setCaption: function(text){
			this._data.caption = text;
			this.el.child('.caption').update(text);
			this.el.child('.caption').setDisplayed( text ? true : false);
		},

		showCaptionEditor: function(setPosition){
			if (!this.captionEditor) {
				this.captionEditor = new Ext.Editor({
					alignment: 'tl',
					autoSize: true,
					field: {
						selectOnFocus: true,
						xtype: 'textfield',
						width: '100%',
						anchor: '99%'
					}
				});
			}
			this.el.child('.caption').setDisplayed(true);
			// if the user clicks the upper 'settings' menu; the caption el should be displayed there,
			//... not below the image, where it might be "below the fold"
			if (!setPosition) {
				this.el.child('.caption').setStyle('position', 'static');
			}
			this.captionEditor.startEdit(this.el.child('.caption'), this._data.caption);
			this.captionEditor.on('complete', function(f, v){
				this.setCaption(v);
				this.el.child('.caption').setStyle('position','absolute');
			}, this);
		},

		getMenuOptions: function(){
			return [{
				group: '',
				text: 'Add / remove caption',
				handler: this.showCaptionEditor.createDelegate(this, [false])
			},{
				group :'',
				text: __('Add / remove animation classes'),
				handler: this.showAnimClassesDialog
			}];

		},

		/**
		 * After pick:
		 */
		pickerHandler: function(sel, afterInitCb){
			this._data = {
				id: sel.data.id,
				caption: sel.data.caption
			};
			var args = Array.prototype.slice.call(arguments);
			args.shift();
			afterInitCb.call(this, args);
		},

		/**
		 *
		 * @param {Object} afterInitCb
		 */
		beforeInit: function(afterInitCb){
			var args = arguments;
			// Do we need to present a dialog or not?
			if(this._data && this._data[this.idProperty]){
				afterInitCb.call(this, args);
				return;
			}
			var picker = new Garp.ModelPickerWindow({
				model: this.model,
				listeners: {
					select: function(sel){
						if (sel.selected) {
							this.pickerHandler(sel.selected, afterInitCb);
						} else {
							this.destroy();
						}
						picker.close();
					},
					scope: this
				}
			});
			picker.show();
		},

		/**
		 * Sets content height based on width (maintains aspect ratio)
		 * @param {Number} new width
		 * @returns height
		 */
		resizeContent: function(nw){
			var i = this._data;
			var aspct = i.height / i.width;
			var nHeight = (nw * aspct) - this.margin;
			this.contentEditableEl.setHeight(nHeight);
			if (this.contentEditableEl.child('.img')) {
				this.contentEditableEl.child('.img').setHeight(nHeight);
			}
			return nHeight;
		},

		setContent: function(){
			this.contentEditableEl = this.el.child('.contenteditable');
			this.contentEditableEl.update('');
			this.contentEditableEl.dom.setAttribute('contenteditable', false);

			var i = new Image();
			var scope = this;
			var path = IMAGES_CDN + 'scaled/cms_preview/' + this._data[this.idProperty];
			i.onerror = function(){
				scope.contentEditableEl.setStyle({
					position: 'relative',
					padding: 0
				});
				scope.contentEditableEl.update('
' + __('Image not found') + '
');
			};
			i.onload = function(){

				Ext.apply(scope._data, {
					width: i.width,
					height: i.height
				});

				scope.contentEditableEl.setStyle({
					position: 'relative',
					padding: 0
				});

				scope.contentEditableEl.update('

');
				scope.contentEditableEl.child('.img').setStyle({
					backgroundImage: 'url("' + path + '")'
				});
				var captionEl = scope.contentEditableEl.child('.caption');
				if (scope._data.caption) {
					captionEl.update(scope._data.caption);
					captionEl.show();
					captionEl.on('click', scope.showCaptionEditor, scope);
				} else {
					captionEl.hide();
				}

				scope.resizeContent(scope.contentEditableEl.getWidth());
				if (scope.ownerCt) {
					scope.ownerCt.doLayout();
				}
			};
			i.src = path;
			if (i.complete) {
				i.onload();
			}
		},

		/**
		 * init!
		 * @param {Object} ct
		 */
		initComponent: function(ct){

			this.html += '

';

			this.addClass('wysiwyg-image');
			this.addClass('wysiwyg-box');
			if (this.col) {
				this.addClass(this.col);
			}
			this.on('user-resize', function(w, nw){
				this.setHeight(this.resizeContent(nw));
			});
			this.on('afterrender', this.setContent, this);
			Garp.dataTypes.Image.Wysiwyg.superclass.initComponent.call(this, arguments);
		}
	});
});
<commit_msg>Modified image upload path<commit_after>Ext.ns('Garp.dataTypes');
Garp.dataTypes.Image.on('init', function(){

	this.iconCls = 'icon-img';

	// Thumbnail column:
	this.insertColumn(0, {
		header: '' + __('Image') + '',
		dataIndex: 'id',
		width: 84,
		fixed: true,
		renderer: Garp.renderers.imageRelationRenderer,
		hidden: false
	});

	this.addListener('loaddata', function(rec, formPanel){
		function updateUI(){
			formPanel.preview.update(Garp.renderers.imagePreviewRenderer(rec.get('filename'), null, rec));
			formPanel.download.update({
				filename: rec.get('filename')
			});
		}
		if (formPanel.rendered) {
			updateUI();
		} else {
			formPanel.on('show', updateUI, null, {
				single: true
			});
		}
		// if we're in a relateCreateWindow, set it's height again, otherwise it might not fit.
		if (typeof formPanel.center == 'function' && rec.get('filename')) {
			formPanel.setHeight(440);
			formPanel.center();
		}
	}, true);

	// Remove these fields, cause we are about to change the order and appearance of them...
	this.removeField('caption');
	this.removeField('filename');
	this.removeField('id');

	var fDesc = this.getField('filename_info');
	if (fDesc) {
		this.removeField('filename_info');
	} else {
		fDesc = {
			xtype: 'box',
			cls: 'garp-notification-boxcomponent',
			name: 'filename_info',
			html: __('Only {1} and {2} files with a maximum of {3} MB are accepted', 'jpg, png', 'gif', '20'),
			fieldLabel: ' '
		};
	}

	// ...include them again:
	this.insertField(0, {
		xtype: 'fieldset',
		style: 'margin: 0;padding:0;',
		items: [{
			name: 'id',
			hideFieldLabel: true,
			disabled: true,
			xtype: 'numberfield',
			hidden: true,
			ref: '../../../../_id'
		}, {
			name: 'filename',
			fieldLabel: __('Filename'),
			xtype: 'uploadfield',
			allowBlank: false,
			emptyText: __('Drag image here, or click browse button'),
			uploadURL: BASE + 'g/content/upload/image',
			ref: '../../../../filename',

			listeners: {
				'change': function(field, val){
					if (this.refOwner._id.getValue()) {
						var url = BASE + 'admin?' +
						Ext.urlEncode({
							model: Garp.currentModel,
							id: this.refOwner._id.getValue()
						});
						if (DEBUG) {
							url += '#DEBUG';
						}

						// because images won't reload if their ID is
						// still the same, we need to reload the page
						this.refOwner.formcontent.on('loaddata', function(){
							var lm = new Ext.LoadMask(Ext.getBody());
							lm.show();
							document.location.href = url;
						});
						this.refOwner.fireEvent('save-all');
					} else {
						field.originalValue = ''; // mark dirty
						this.refOwner.preview.update(Garp.renderers.uploadedImagePreviewRenderer(val));
						this.refOwner.download.update({
							filename: val
						});
						this.refOwner.fireEvent('save-all');
					}
					return true;
				}
			}
		}, fDesc, {
			name: 'caption',
			xtype: 'textfield',
			fieldLabel: __('Caption')
		}, {
			xtype: 'box',
			ref: '../../../../preview',
			fieldLabel: __('Preview'),
			cls: 'preview',
			html: ''
		}, {
			xtype: 'box',
			hidden: false,
			ref: '../../../../download',
			fieldLabel: ' ',
			hideFieldLabel: false,
			tpl: new Ext.XTemplate('<tpl if="filename">', '' + __('View original file') + '', '</tpl>')
		}]
	});

	// Wysiwyg Editor
	this.Wysiwyg = Ext.extend(Garp.WysiwygAbstract, {

		model: 'Image',

		idProperty: 'id',

		settingsMenu: true,

		margin: 0,

		getData: function(){
			return {
				id: this._data.id,
				caption: this._data.caption
			};
		},

		// override: we don't need filtering for images:
		filterHtml: function(){
			return true;
		},

		setCaption: function(text){
			this._data.caption = text;
			this.el.child('.caption').update(text);
			this.el.child('.caption').setDisplayed( text ? true : false);
		},

		showCaptionEditor: function(setPosition){
			if (!this.captionEditor) {
				this.captionEditor = new Ext.Editor({
					alignment: 'tl',
					autoSize: true,
					field: {
						selectOnFocus: true,
						xtype: 'textfield',
						width: '100%',
						anchor: '99%'
					}
				});
			}
			this.el.child('.caption').setDisplayed(true);
			// if the user clicks the upper 'settings' menu; the caption el should be displayed there,
			//... not below the image, where it might be "below the fold"
			if (!setPosition) {
				this.el.child('.caption').setStyle('position', 'static');
			}
			this.captionEditor.startEdit(this.el.child('.caption'), this._data.caption);
			this.captionEditor.on('complete', function(f, v){
				this.setCaption(v);
				this.el.child('.caption').setStyle('position','absolute');
			}, this);
		},

		getMenuOptions: function(){
			return [{
				group: '',
				text: 'Add / remove caption',
				handler: this.showCaptionEditor.createDelegate(this, [false])
			},{
				group :'',
				text: __('Add / remove animation classes'),
				handler: this.showAnimClassesDialog
			}];

		},

		/**
		 * After pick:
		 */
		pickerHandler: function(sel, afterInitCb){
			this._data = {
				id: sel.data.id,
				caption: sel.data.caption
			};
			var args = Array.prototype.slice.call(arguments);
			args.shift();
			afterInitCb.call(this, args);
		},

		/**
		 *
		 * @param {Object} afterInitCb
		 */
		beforeInit: function(afterInitCb){
			var args = arguments;
			// Do we need to present a dialog or not?
			if(this._data && this._data[this.idProperty]){
				afterInitCb.call(this, args);
				return;
			}
			var picker = new Garp.ModelPickerWindow({
				model: this.model,
				listeners: {
					select: function(sel){
						if (sel.selected) {
							this.pickerHandler(sel.selected, afterInitCb);
						} else {
							this.destroy();
						}
						picker.close();
					},
					scope: this
				}
			});
			picker.show();
		},

		/**
		 * Sets content height based on width (maintains aspect ratio)
		 * @param {Number} new width
		 * @returns height
		 */
		resizeContent: function(nw){
			var i = this._data;
			var aspct = i.height / i.width;
			var nHeight = (nw * aspct) - this.margin;
			this.contentEditableEl.setHeight(nHeight);
			if (this.contentEditableEl.child('.img')) {
				this.contentEditableEl.child('.img').setHeight(nHeight);
			}
			return nHeight;
		},

		setContent: function(){
			this.contentEditableEl = this.el.child('.contenteditable');
			this.contentEditableEl.update('');
			this.contentEditableEl.dom.setAttribute('contenteditable', false);

			var i = new Image();
			var scope = this;
			var path = IMAGES_CDN + 'scaled/cms_preview/' + this._data[this.idProperty];
			i.onerror = function(){
				scope.contentEditableEl.setStyle({
					position: 'relative',
					padding: 0
				});
				scope.contentEditableEl.update('
' + __('Image not found') + '
');
			};
			i.onload = function(){

				Ext.apply(scope._data, {
					width: i.width,
					height: i.height
				});

				scope.contentEditableEl.setStyle({
					position: 'relative',
					padding: 0
				});

				scope.contentEditableEl.update('

');
				scope.contentEditableEl.child('.img').setStyle({
					backgroundImage: 'url("' + path + '")'
				});
				var captionEl = scope.contentEditableEl.child('.caption');
				if (scope._data.caption) {
					captionEl.update(scope._data.caption);
					captionEl.show();
					captionEl.on('click', scope.showCaptionEditor, scope);
				} else {
					captionEl.hide();
				}

				scope.resizeContent(scope.contentEditableEl.getWidth());
				if (scope.ownerCt) {
					scope.ownerCt.doLayout();
				}
			};
			i.src = path;
			if (i.complete) {
				i.onload();
			}
		},

		/**
		 * init!
		 * @param {Object} ct
		 */
		initComponent: function(ct){

			this.html += '

';

			this.addClass('wysiwyg-image');
			this.addClass('wysiwyg-box');
			if (this.col) {
				this.addClass(this.col);
			}
			this.on('user-resize', function(w, nw){
				this.setHeight(this.resizeContent(nw));
			});
			this.on('afterrender', this.setContent, this);
			Garp.dataTypes.Image.Wysiwyg.superclass.initComponent.call(this, arguments);
		}
	});
});
<|endoftext|>


<commit_before>var child = require('child_process');

exports.exec = function(url, filename, options, cb){
	var key,
		stdin = ['phantomjs'];

	stdin.push(options.args);
	stdin.push(__dirname+'/render.js');
	stdin.push("'"+url+"'");
	stdin.push("'"+filename+"'");
	stdin.push("'"+JSON.stringify(options)+"'");

	return child.exec(stdin.join(' '), function(err, stdo, stde){
		cb ? cb(err) : null;
	});
};

exports.supports = function(cb, cmd) {
	var stream = child.exec('which '+(cmd || 'phantomjs'), function(err, stdo, stde){
		return cb(!!stdo.toString());
	});
};
<commit_msg>fixed #25<commit_after>var child = require('child_process');

var which = process.platform == 'win32' ? 'where' : 'which';

exports.exec = function(url, filename, options, cb){
	var key,
		stdin = ['phantomjs'];

	stdin.push(options.args);
	stdin.push(__dirname+'/render.js');
	stdin.push("'"+url+"'");
	stdin.push("'"+filename+"'");
	stdin.push("'"+JSON.stringify(options)+"'");

	return child.exec(stdin.join(' '), function(err, stdo, stde){
		cb ? cb(err) : null;
	});
};

exports.supports = function(cb, cmd) {
	var stream = child.exec(which+' '+(cmd || 'phantomjs'), function(err, stdo, stde){
		return cb(!!stdo.toString());
	});
};
<|endoftext|>


<commit_before>var send = function(){
    var addr = $("#addr option:selected").val();
    var cmd = $("#cmd option:selected").val();
    var del = $('#del').val();
    var bri = $('#bri').val();
    var rgb = $('#rgb').val();
    var tout = $('#tout').val();

    R = hexToR(rgb);
    G = hexToG(rgb);
    B = hexToB(rgb);

    rgb = R+','+G+','+B;
    run(addr,cmd,del,bri,rgb,tout);
}

var run = function(addr, cmd, del, bri, rgb, tout) {
    var getCmd = "../apis/lights/lights_cmd.php?cmd=" + cmd + "&del=" + del + "&bri="
            + bri + "&rgb=" + rgb + "&tout=" + tout+ '&addr="' + addr+'"';
    console.log('sending command');
    $.get(getCmd, function(d){
        console.log(d);
    });
}

var pTimeout = 0;

var party = function(){
   var bri = '30'
   run('00 00 00 00 00 00 FF FF','P','50',bri,'255,100,0','0');

   $.get('../apis/mona/play_sound_cmd.php?name=/var/www/git/redalert/resources/sounds/gfdr.mp3', function(d){
    console.log(d)
   });

   clearTimeout(pTimeout);
   pTimeout = setTimeout(function(){
        run('00 00 00 00 00 00 FF FF','G','800',bri,'30,120,230','0');

        pTimeout = setTimeout(function(){
            run('00 00 00 00 00 00 FF FF','D','40',bri,'255,100,0','0');

            pTimeout = setTimeout(function(){
                run('00 00 00 00 00 00 FF FF','G','800',bri,'78,174,71','0');
                pTimeout = setTimeout(function(){
                      run('00 00 00 00 00 00 FF FF','D','40',bri,'255,100,0','0');
                },7000);
            },15000);
        },15000);

   },15000);
}

var stop = function(){
    clearTimeout(pTimeout);
   $.get('../apis/mona/kill_player_cmd.php');
   run('00 00 00 00 00 00 FF FF','O','50','0','255,100,0','1');
}

function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}<commit_msg>updating jobs<commit_after>var send = function(){
    var addr = $("#addr option:selected").val();
    var cmd = $("#cmd option:selected").val();
    var del = $('#del').val();
    var bri = $('#bri').val();
    var rgb = $('#rgb').val();
    var tout = $('#tout').val();

    R = hexToR(rgb);
    G = hexToG(rgb);
    B = hexToB(rgb);

    rgb = R+','+G+','+B;
    run(addr,cmd,del,bri,rgb,tout);
}

var run = function(addr, cmd, del, bri, rgb, tout) {
    var getCmd = "../apis/lights/lights_cmd.php?cmd=" + cmd + "&del=" + del + "&bri="
            + bri + "&rgb=" + rgb + "&tout=" + tout+ '&addr="' + addr+'"';
    console.log('sending command');
    $.get(getCmd, function(d){
        console.log(d);
    });
}

var pTimeout = 0;

var party = function(){
   var bri = '30'
   run('00 00 00 00 00 00 FF FF','P','50',bri,'255,100,0','0');

   $.get('../apis/mona/play_sound_cmd.php?name=/var/www/git/redalert/resources/sounds/gfdr.mp3', function(d){
    console.log(d)
   });

   clearTimeout(pTimeout);
   pTimeout = setTimeout(function(){
       run('00 00 00 00 00 00 FF FF','D','40',bri,'255,100,0','0');

   },14000);
}

var stop = function(){
    clearTimeout(pTimeout);
   $.get('../apis/mona/kill_player_cmd.php');
   run('00 00 00 00 00 00 FF FF','O','50','0','255,100,0','1');
}

function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}<|endoftext|>


<commit_before>YUI.add('aui-datatable-tests', function(Y) {

    var suite = new Y.Test.Suite('aui-datatable');

    var data = [
        {
            active: 'no',
            address: '3271 Another Ave',
            amount: 3,
            city: 'New York',
            colors: ['red', 'blue'],
            date: '2013-01-01',
            fruit: ['apple'],
            name: 'Joan B. Jones',
            state: 'AL'
        },
        {
            active: 'maybe',
            address: '9996 Random Road',
            amount: 0,
            city: 'Los Angeles',
            colors: ['green'],
            date: '2013-01-01',
            fruit: ['cherry'],
            name: 'Bob C. Uncle',
            state: 'CA'
        },
        {
            active: 'yes',
            address: '1623 Some Street',
            amount: 5,
            city: 'San Francisco',
            colors: ['red'],
            date: '',
            fruit: ['cherry'],
            name: 'John D. Smith',
            state: 'CA'
        },
        {
            active: 'no',
            address: '3217 Another Ave',
            amount: 3,
            city: 'New York',
            colors: ['red', 'blue'],
            date: '2013-01-06',
            fruit: ['apple', 'cherry'],
            name: 'Joan E. Jones',
            state: 'KY'
        }
    ];

    new Y.DataTable({
        boundingBox: '#simple',
        columns: [
            {
                editor: new Y.TextAreaCellEditor({
                    on: {
                        cancel: function(event) {
                            console.log('cancel', event);
                        },
                        save: function(event) {
                            console.log('save', event.newVal);
                        }
                    },
                    validator: {
                        rules: {
                            value: {
                                required: true
                            }
                        }
                    }
                }),
                key: 'name',
                sortable: true
            },
            {
                editor: new Y.TextAreaCellEditor(),
                key: 'address'
            },
            {
                editor: new Y.TextAreaCellEditor(),
                key: 'city'
            },
            {
                editor: new Y.DropDownCellEditor({
                    editable: true,
                    options: ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID',
                        'IL', 'IN', 'IA', 'KS', 'KY', 'LA']
                }),
                key: 'state'
            },
            'amount',
            {
                editor: new Y.RadioCellEditor({
                    editable: true,
                    options: {
                        yes: 'Yes',
                        no: 'No',
                        maybe: 'Maybe'
                    }
                }),
                key: 'active'
            },
            {
                editor: new Y.CheckboxCellEditor({
                    editable: true,
                    multiple: true,
                    options: {
                        red: 'Red',
                        green: 'Green',
                        blue: 'Blue'
                    }
                }),
                key: 'colors'
            },
            {
                editor: new Y.DropDownCellEditor({
                    editable: true,
                    multiple: true,
                    options: {
                        apple: 'Apple',
                        banana: 'Banana',
                        cherry: 'Cherry',
                        kiwi: 'Kiwi'
                    }
                }),
                key: 'fruit',
                sortable: true
            },
            {
                editor: new Y.DateCellEditor({
                    calendar: {
                        selectionMode: 'multiple',
                        showNextMonth: true,
                        showPrevMonth: true,
                        width: '400px'
                    }
                }),
                key: 'date',
                sortable: true
            }
        ],
        data: data,
        editEvent: 'dblclick',
        plugins: [
            {
                fn: Y.Plugin.DataTableHighlight
            }
        ]
    }).render();

    function assertHighlightPosition(tableData) {
        var lowerXLimit,
            lowerYLimit,
            overlayActiveBorderWidth,
            overlayActiveBottomWidth,
            overlayActiveChildren,
            overlayActiveLeftHeight,
            overlayActiveNode,
            overlayActiveRightHeight,
            overlayActiveTopWidth,
            overlaySelectionNode,
            tableDataHeight,
            tableDataWidth,
            upperXLimit,
            upperYLimit;

        overlayActiveNode = Y.one('.table-highlight-overlay-active');
        overlayActiveChildren = overlayActiveNode.get('children');
        overlaySelectionNode = overlayActiveNode ? overlayActiveNode.next('.table-highlight-overlay') : null;

        lowerXLimit = tableData.getX() >= (overlayActiveNode.getX() - 1);
        lowerYLimit = tableData.getY() >= (overlayActiveNode.getY() - 1);

        overlayActiveBorderWidth = overlayActiveChildren.item(0).get('offsetHeight');
        overlayActiveBottomWidth = overlayActiveChildren.item(2).get('offsetWidth');

        overlayActiveLeftHeight = overlayActiveChildren.item(3).get('offsetHeight') + overlayActiveBorderWidth;
        overlayActiveRightHeight = overlayActiveChildren.item(1).get('offsetHeight') + overlayActiveBorderWidth;

        overlayActiveTopWidth = overlayActiveChildren.item(0).get('offsetWidth');

        tableDataHeight = tableData.get('offsetHeight');
        tableDataWidth = tableData.get('offsetWidth');

        upperXLimit = tableData.getX() <= (overlayActiveNode.getX() + 1);
        upperYLimit = tableData.getY() <= (overlayActiveNode.getY() + 1);

        Y.Assert.isTrue(
            (lowerXLimit && upperXLimit),
            'X Position of 	 and highlight are not the similar.');

        Y.Assert.isTrue(
            (lowerYLimit && upperYLimit),
            'Y Position of 	 and highlight are not the similar.');

        Y.Assert.areSame(
            tableDataWidth,
            overlayActiveBottomWidth,
            'Hightlight bottom border width ' + overlayActiveBottomWidth + ' is not same width as table data ' +
            tableDataWidth + '.');

        Y.Assert.areSame(
            tableDataWidth,
            overlayActiveTopWidth,
            'Hightlight top border width ' + overlayActiveTopWidth + ' is not same width as table data ' +
            tableDataWidth + '.');

        Y.Assert.areSame(
            tableDataHeight,
            overlayActiveLeftHeight,
            'Hightlight left border height ' + overlayActiveLeftHeight + ' is not same height as table data ' +
            tableDataHeight + '.');

        Y.Assert.areSame(
            tableDataHeight,
            overlayActiveRightHeight,
            'Hightlight right border height ' + overlayActiveRightHeight + ' is not same height as table data ' +
            tableDataHeight + '.');
    }

    suite.add(new Y.Test.Case({
        name: 'Datatable Highlight',
        'table-cell should be highlighted on click': function() {
            var test = this,
                tableData;

            tableData = Y.one('tr.table-odd td.table-col-amount');

            tableData.once('mousedown', function(event) {
                setTimeout(function() {
                    test.resume(function() {
                        assertHighlightPosition(event.currentTarget);
                    });
                }, 800);
            });

            setTimeout(function() {
                tableData.simulate('mousedown');
                tableData.simulate('mouseup');
            }, 0);

            test.wait(1000);
        },

        'table-cell should still be highlighted when table is scrolled horizontally': function() {
            var test = this,
                datatableContainer,
                datatableContent,
                tableData;

            datatableContainer = Y.one('#simple');
            datatableContent = datatableContainer.one('.table-content');
            tableData = Y.one('tr.table-odd td.table-col-amount');

            datatableContainer.setStyle('width', '300px');
            datatableContent.setStyle('overflow', 'auto');

            tableData.once('mousedown', function(event) {
                setTimeout(function() {
                    test.resume(function() {
                        assertHighlightPosition(event.currentTarget);
                    });
                }, 800);
            });

            setTimeout(function() {
                tableData.simulate('mousedown');
                tableData.simulate('mouseup');
                datatableContent.set('scrollLeft', 200);
            }, 0);

            test.wait(1000);
        }
    }));

    suite.add(new Y.Test.Case({
        name: 'Datatable',

        init: function() {
            var data = [
                {
                    active: 'no',
                    address: '3271 Another Ave',
                    amount: 3,
                    city: 'New York',
                    colors: ['red','blue'],
                    date: '2013-01-01',
                    fruit: ['apple'],
                    name: 'Joan B. Jones',
                    state: 'AL'
                },
                {
                    active: 'maybe',
                    address: '9996 Random Road',
                    amount: 0,
                    city: 'Los Angeles',
                    colors: ['green'],
                    date: '2013-01-01',
                    fruit: ['cherry'],
                    name: 'Bob C. Uncle',
                    state: 'CA'
                },
                {
                    active: 'yes',
                    address: '1623 Some Street',
                    amount: 5,
                    city: 'San Francisco',
                    colors: ['red'],
                    date: '',
                    fruit: ['cherry'],
                    name: 'John D. Smith',
                    state: 'CA'
                },
                {
                    active: 'no',
                    address: '3217 Another Ave',
                    amount: 3,
                    city: 'New York',
                    colors: ['red','blue'],
                    date: '2013-01-06',
                    fruit: ['apple','cherry'],
                    name: 'Joan E. Jones',
                    state: 'KY'
                }
            ];

            this._dataTable = new Y.DataTable({
                cssClass: 'table-striped',
                boundingBox: '#datatable',
                columns: [
                    {
                        key: 'name',
                        sortable: true,
                        editor: new Y.TextAreaCellEditor({
                            validator: {
                                rules: {
                                    value: {
                                        required: true
                                    }
                                }
                            }
                        })
                    },
                    {
                        key: 'address',
                        editor: new Y.TextAreaCellEditor()
                    },
                    {
                        key: 'city',
                        editor: new Y.TextAreaCellEditor()
                    },
                    {
                        key: 'state',
                        editor: new Y.DropDownCellEditor({
                            editable: true,
                            options: ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA']
                        })
                    },
                    'amount',
                    {
                        key:'active',
                        editor: new Y.RadioCellEditor({
                            editable: true,
                            options: {
                                yes: 'Yes',
                                no: 'No',
                                maybe: 'Maybe'
                            }
                        })
                    },
                    {
                        key:'colors',
                        editor: new Y.CheckboxCellEditor({
                            editable: true,
                            multiple: true,
                            options: {
                                red: 'Red',
                                green: 'Green',
                                blue: 'Blue'
                            }
                        })
                    },
                    {
                        key: 'fruit',
                        sortable: true,
                        editor: new Y.DropDownCellEditor({
                            editable: true,
                            multiple: true,
                            options: {
                                apple: 'Apple',
                                cherry: 'Cherry',
                                banana: 'Banana',
                                kiwi: 'Kiwi'
                            }
                        })
                    },
                    {
                        key: 'date',
                        sortable: true,
                        editor: new Y.DateCellEditor({
                            calendar: {
                                width:'400px',
                                showPrevMonth: true,
                                showNextMonth: true,
                                selectionMode: 'multiple'
                            }
                        })
                    }
                ],
                data: data,
                editEvent: 'dblclick',
                plugins: [
                    {
                        fn: Y.Plugin.DataTableHighlight
                    }
                ]
            }).render();
        },

        'navigate on arrow keys': function() {
            var dataTable = this._dataTable,
                activeCoords,
                boundingBox = dataTable.get('boundingBox'),
                cellCoords = {
                    topLeft: [0, 0],
                    topRight: [0, 1],
                    bottomLeft: [1, 0],
                    bottomRight: [1, 1]
                };

            dataTable.focus();
            dataTable.set('activeCoord', cellCoords.topLeft);
            dataTable.set('selection', cellCoords.topLeft);

            //simulate right arrow key press

            boundingBox.simulate('keydown', { keyCode: 39 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.topRight, activeCoords, 'Right arrow should have moved selection.');

            //simulate down arrow key press

            boundingBox.simulate('keydown', { keyCode: 40 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.bottomRight, activeCoords, 'Down arrow should have moved selection.');

            //simulate left arrow key press

            boundingBox.simulate('keydown', { keyCode: 37 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.bottomLeft, activeCoords, 'Left arrow should have moved selection.');

            //simulate up arrow key press

            boundingBox.simulate('keydown', { keyCode: 38 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.topLeft, activeCoords, 'Up arrow should have moved selection.');
        },

        'open editor on double click': function() {
            var dataTable = this._dataTable,
                firstCell = dataTable.getCell([0, 0]);

            firstCell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor');

            Y.Assert.isNotNull(editorNode, 'The editor should have been opened.');
        },

        'cancel cell editor': function() {
            var cell = this._dataTable.getCell([0, 0]),
                newVal = 'Testing editor node save.',
                originalVal = cell.html();

            cell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor'),
                cancelBtn = editorNode.all('button').item(1),
                textArea = editorNode.one('textarea');

            textArea.val(newVal);

            cancelBtn.focus().simulate('click');

            Y.Assert.areEqual(cell.html(), originalVal);
        },

        'save cell edit': function() {
            var cell = this._dataTable.getCell([0, 0]),
                newVal = 'Testing editor node save.';

            cell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor'),
                textArea = editorNode.one('textarea'),
                saveBtn = editorNode.all('button').item(0);

            textArea.val(newVal);

            saveBtn.focus().simulate('click');

            Y.Assert.areEqual(cell.html(), newVal);
        },

        'fields sorted on caret click': function() {
            var sortableHeader = Y.one('.table-sortable-column'),
                colId = sortableHeader.getData('yui3-col-id'),
                columnList = Y.all('.table-cell.table-col-' + colId);

            sortableHeader.simulate('click');

            var sortBy = sortableHeader.get('aria-sort');

            Y.Assert.isTrue(
                columnList.some(
                    function(item, index) {
                        var sorted;

                        if (index > 0) {
                            var html = item.html(),
                                prevCell = columnList.item(index - 1),
                                prevCellHtml = prevCell.html();

                            if (sortBy === 'ascending') {
                                sorted = prevCellHtml <= html;
                            }
                            else {
                                sorted = prevCellHtml >= html;
                            }

                            prevCellHtml = html;
                        }
                        else {
                            sorted = true;
                        }

                        return sorted;
                    }
                ),
                'Items are not sorted correctly.'
            );
        }
    }));

    Y.Test.Runner.add(suite);

}, '', {
    requires: ['aui-datatable', 'node', 'node-event-simulate', 'test']
});
<commit_msg>AUI-1288 - fixed logic for 'fields sorted on caret click' test<commit_after>YUI.add('aui-datatable-tests', function(Y) {

    var suite = new Y.Test.Suite('aui-datatable');

    var data = [
        {
            active: 'no',
            address: '3271 Another Ave',
            amount: 3,
            city: 'New York',
            colors: ['red', 'blue'],
            date: '2013-01-01',
            fruit: ['apple'],
            name: 'Joan B. Jones',
            state: 'AL'
        },
        {
            active: 'maybe',
            address: '9996 Random Road',
            amount: 0,
            city: 'Los Angeles',
            colors: ['green'],
            date: '2013-01-01',
            fruit: ['cherry'],
            name: 'Bob C. Uncle',
            state: 'CA'
        },
        {
            active: 'yes',
            address: '1623 Some Street',
            amount: 5,
            city: 'San Francisco',
            colors: ['red'],
            date: '',
            fruit: ['cherry'],
            name: 'John D. Smith',
            state: 'CA'
        },
        {
            active: 'no',
            address: '3217 Another Ave',
            amount: 3,
            city: 'New York',
            colors: ['red', 'blue'],
            date: '2013-01-06',
            fruit: ['apple', 'cherry'],
            name: 'Joan E. Jones',
            state: 'KY'
        }
    ];

    new Y.DataTable({
        boundingBox: '#simple',
        columns: [
            {
                editor: new Y.TextAreaCellEditor({
                    on: {
                        cancel: function(event) {
                            console.log('cancel', event);
                        },
                        save: function(event) {
                            console.log('save', event.newVal);
                        }
                    },
                    validator: {
                        rules: {
                            value: {
                                required: true
                            }
                        }
                    }
                }),
                key: 'name',
                sortable: true
            },
            {
                editor: new Y.TextAreaCellEditor(),
                key: 'address'
            },
            {
                editor: new Y.TextAreaCellEditor(),
                key: 'city'
            },
            {
                editor: new Y.DropDownCellEditor({
                    editable: true,
                    options: ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID',
                        'IL', 'IN', 'IA', 'KS', 'KY', 'LA']
                }),
                key: 'state'
            },
            'amount',
            {
                editor: new Y.RadioCellEditor({
                    editable: true,
                    options: {
                        yes: 'Yes',
                        no: 'No',
                        maybe: 'Maybe'
                    }
                }),
                key: 'active'
            },
            {
                editor: new Y.CheckboxCellEditor({
                    editable: true,
                    multiple: true,
                    options: {
                        red: 'Red',
                        green: 'Green',
                        blue: 'Blue'
                    }
                }),
                key: 'colors'
            },
            {
                editor: new Y.DropDownCellEditor({
                    editable: true,
                    multiple: true,
                    options: {
                        apple: 'Apple',
                        banana: 'Banana',
                        cherry: 'Cherry',
                        kiwi: 'Kiwi'
                    }
                }),
                key: 'fruit',
                sortable: true
            },
            {
                editor: new Y.DateCellEditor({
                    calendar: {
                        selectionMode: 'multiple',
                        showNextMonth: true,
                        showPrevMonth: true,
                        width: '400px'
                    }
                }),
                key: 'date',
                sortable: true
            }
        ],
        data: data,
        editEvent: 'dblclick',
        plugins: [
            {
                fn: Y.Plugin.DataTableHighlight
            }
        ]
    }).render();

    function assertHighlightPosition(tableData) {
        var lowerXLimit,
            lowerYLimit,
            overlayActiveBorderWidth,
            overlayActiveBottomWidth,
            overlayActiveChildren,
            overlayActiveLeftHeight,
            overlayActiveNode,
            overlayActiveRightHeight,
            overlayActiveTopWidth,
            overlaySelectionNode,
            tableDataHeight,
            tableDataWidth,
            upperXLimit,
            upperYLimit;

        overlayActiveNode = Y.one('.table-highlight-overlay-active');
        overlayActiveChildren = overlayActiveNode.get('children');
        overlaySelectionNode = overlayActiveNode ? overlayActiveNode.next('.table-highlight-overlay') : null;

        lowerXLimit = tableData.getX() >= (overlayActiveNode.getX() - 1);
        lowerYLimit = tableData.getY() >= (overlayActiveNode.getY() - 1);

        overlayActiveBorderWidth = overlayActiveChildren.item(0).get('offsetHeight');
        overlayActiveBottomWidth = overlayActiveChildren.item(2).get('offsetWidth');

        overlayActiveLeftHeight = overlayActiveChildren.item(3).get('offsetHeight') + overlayActiveBorderWidth;
        overlayActiveRightHeight = overlayActiveChildren.item(1).get('offsetHeight') + overlayActiveBorderWidth;

        overlayActiveTopWidth = overlayActiveChildren.item(0).get('offsetWidth');

        tableDataHeight = tableData.get('offsetHeight');
        tableDataWidth = tableData.get('offsetWidth');

        upperXLimit = tableData.getX() <= (overlayActiveNode.getX() + 1);
        upperYLimit = tableData.getY() <= (overlayActiveNode.getY() + 1);

        Y.Assert.isTrue(
            (lowerXLimit && upperXLimit),
            'X Position of 	 and highlight are not the similar.');

        Y.Assert.isTrue(
            (lowerYLimit && upperYLimit),
            'Y Position of 	 and highlight are not the similar.');

        Y.Assert.areSame(
            tableDataWidth,
            overlayActiveBottomWidth,
            'Hightlight bottom border width ' + overlayActiveBottomWidth + ' is not same width as table data ' +
            tableDataWidth + '.');

        Y.Assert.areSame(
            tableDataWidth,
            overlayActiveTopWidth,
            'Hightlight top border width ' + overlayActiveTopWidth + ' is not same width as table data ' +
            tableDataWidth + '.');

        Y.Assert.areSame(
            tableDataHeight,
            overlayActiveLeftHeight,
            'Hightlight left border height ' + overlayActiveLeftHeight + ' is not same height as table data ' +
            tableDataHeight + '.');

        Y.Assert.areSame(
            tableDataHeight,
            overlayActiveRightHeight,
            'Hightlight right border height ' + overlayActiveRightHeight + ' is not same height as table data ' +
            tableDataHeight + '.');
    }

    suite.add(new Y.Test.Case({
        name: 'Datatable Highlight',
        'table-cell should be highlighted on click': function() {
            var test = this,
                tableData;

            tableData = Y.one('tr.table-odd td.table-col-amount');

            tableData.once('mousedown', function(event) {
                setTimeout(function() {
                    test.resume(function() {
                        assertHighlightPosition(event.currentTarget);
                    });
                }, 800);
            });

            setTimeout(function() {
                tableData.simulate('mousedown');
                tableData.simulate('mouseup');
            }, 0);

            test.wait(1000);
        },

        'table-cell should still be highlighted when table is scrolled horizontally': function() {
            var test = this,
                datatableContainer,
                datatableContent,
                tableData;

            datatableContainer = Y.one('#simple');
            datatableContent = datatableContainer.one('.table-content');
            tableData = Y.one('tr.table-odd td.table-col-amount');

            datatableContainer.setStyle('width', '300px');
            datatableContent.setStyle('overflow', 'auto');

            tableData.once('mousedown', function(event) {
                setTimeout(function() {
                    test.resume(function() {
                        assertHighlightPosition(event.currentTarget);
                    });
                }, 800);
            });

            setTimeout(function() {
                tableData.simulate('mousedown');
                tableData.simulate('mouseup');
                datatableContent.set('scrollLeft', 200);
            }, 0);

            test.wait(1000);
        }
    }));

    suite.add(new Y.Test.Case({
        name: 'Datatable',

        init: function() {
            var data = [
                {
                    active: 'no',
                    address: '3271 Another Ave',
                    amount: 3,
                    city: 'New York',
                    colors: ['red','blue'],
                    date: '2013-01-01',
                    fruit: ['apple'],
                    name: 'Joan B. Jones',
                    state: 'AL'
                },
                {
                    active: 'maybe',
                    address: '9996 Random Road',
                    amount: 0,
                    city: 'Los Angeles',
                    colors: ['green'],
                    date: '2013-01-01',
                    fruit: ['cherry'],
                    name: 'Bob C. Uncle',
                    state: 'CA'
                },
                {
                    active: 'yes',
                    address: '1623 Some Street',
                    amount: 5,
                    city: 'San Francisco',
                    colors: ['red'],
                    date: '',
                    fruit: ['cherry'],
                    name: 'John D. Smith',
                    state: 'CA'
                },
                {
                    active: 'no',
                    address: '3217 Another Ave',
                    amount: 3,
                    city: 'New York',
                    colors: ['red','blue'],
                    date: '2013-01-06',
                    fruit: ['apple','cherry'],
                    name: 'Joan E. Jones',
                    state: 'KY'
                }
            ];

            this._dataTable = new Y.DataTable({
                cssClass: 'table-striped',
                boundingBox: '#datatable',
                columns: [
                    {
                        key: 'name',
                        sortable: true,
                        editor: new Y.TextAreaCellEditor({
                            validator: {
                                rules: {
                                    value: {
                                        required: true
                                    }
                                }
                            }
                        })
                    },
                    {
                        key: 'address',
                        editor: new Y.TextAreaCellEditor()
                    },
                    {
                        key: 'city',
                        editor: new Y.TextAreaCellEditor()
                    },
                    {
                        key: 'state',
                        editor: new Y.DropDownCellEditor({
                            editable: true,
                            options: ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA']
                        })
                    },
                    'amount',
                    {
                        key:'active',
                        editor: new Y.RadioCellEditor({
                            editable: true,
                            options: {
                                yes: 'Yes',
                                no: 'No',
                                maybe: 'Maybe'
                            }
                        })
                    },
                    {
                        key:'colors',
                        editor: new Y.CheckboxCellEditor({
                            editable: true,
                            multiple: true,
                            options: {
                                red: 'Red',
                                green: 'Green',
                                blue: 'Blue'
                            }
                        })
                    },
                    {
                        key: 'fruit',
                        sortable: true,
                        editor: new Y.DropDownCellEditor({
                            editable: true,
                            multiple: true,
                            options: {
                                apple: 'Apple',
                                cherry: 'Cherry',
                                banana: 'Banana',
                                kiwi: 'Kiwi'
                            }
                        })
                    },
                    {
                        key: 'date',
                        sortable: true,
                        editor: new Y.DateCellEditor({
                            calendar: {
                                width:'400px',
                                showPrevMonth: true,
                                showNextMonth: true,
                                selectionMode: 'multiple'
                            }
                        })
                    }
                ],
                data: data,
                editEvent: 'dblclick',
                plugins: [
                    {
                        fn: Y.Plugin.DataTableHighlight
                    }
                ]
            }).render();
        },

        'navigate on arrow keys': function() {
            var dataTable = this._dataTable,
                activeCoords,
                boundingBox = dataTable.get('boundingBox'),
                cellCoords = {
                    topLeft: [0, 0],
                    topRight: [0, 1],
                    bottomLeft: [1, 0],
                    bottomRight: [1, 1]
                };

            dataTable.focus();
            dataTable.set('activeCoord', cellCoords.topLeft);
            dataTable.set('selection', cellCoords.topLeft);

            //simulate right arrow key press

            boundingBox.simulate('keydown', { keyCode: 39 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.topRight, activeCoords, 'Right arrow should have moved selection.');

            //simulate down arrow key press

            boundingBox.simulate('keydown', { keyCode: 40 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.bottomRight, activeCoords, 'Down arrow should have moved selection.');

            //simulate left arrow key press

            boundingBox.simulate('keydown', { keyCode: 37 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.bottomLeft, activeCoords, 'Left arrow should have moved selection.');

            //simulate up arrow key press

            boundingBox.simulate('keydown', { keyCode: 38 });
            activeCoords = dataTable.get('activeCoord');

            Y.ArrayAssert.itemsAreSame(cellCoords.topLeft, activeCoords, 'Up arrow should have moved selection.');
        },

        'open editor on double click': function() {
            var dataTable = this._dataTable,
                firstCell = dataTable.getCell([0, 0]);

            firstCell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor');

            Y.Assert.isNotNull(editorNode, 'The editor should have been opened.');
        },

        'cancel cell editor': function() {
            var cell = this._dataTable.getCell([0, 0]),
                newVal = 'Testing editor node save.',
                originalVal = cell.html();

            cell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor'),
                cancelBtn = editorNode.all('button').item(1),
                textArea = editorNode.one('textarea');

            textArea.val(newVal);

            cancelBtn.focus().simulate('click');

            Y.Assert.areEqual(cell.html(), originalVal);
        },

        'save cell edit': function() {
            var cell = this._dataTable.getCell([0, 0]),
                newVal = 'Testing editor node save.';

            cell.simulate('dblclick');

            var editorNode = Y.one('.basecelleditor'),
                textArea = editorNode.one('textarea'),
                saveBtn = editorNode.all('button').item(0);

            textArea.val(newVal);

            saveBtn.focus().simulate('click');

            Y.Assert.areEqual(cell.html(), newVal);
        },

        'fields sorted on caret click': function() {
            var sortableHeader = Y.one('.table-sortable-column'),
                colId = sortableHeader.getData('yui3-col-id');

            sortableHeader.simulate('click');

            var columnList = Y.all('.table-cell.table-col-' + colId),
                sortBy = sortableHeader.get('aria-sort');

            Y.Assert.isFalse(
                columnList.some(
                    function(item, index) {
                        var sorted;

                        if (index > 0) {
                            var text = item.text().toLowerCase(),
                                prevCell = columnList.item(index - 1),
                                prevCellText = prevCell.text().toLowerCase();

                            if (sortBy === 'ascending') {
                                sorted = prevCellText <= text;
                            }
                            else {
                                sorted = prevCellText >= text;
                            }
                        }
                        else {
                            sorted = true;
                        }

                        return !sorted;
                    }
                ),
                'Items are not sorted correctly.'
            );
        }
    }));

    Y.Test.Runner.add(suite);

}, '', {
    requires: ['aui-datatable', 'node', 'node-event-simulate', 'test']
});
<|endoftext|>