<commit_before>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Module dependencies.
var util = require('util');
var crypto = require('crypto');

var azureutil = require('../../util/util');
var ServiceClient = require('../core/serviceclient');
var StorageServiceClient = require('../core/storageserviceclient');
var WebResource = require('../../http/webresource');
var Constants = require('../../util/constants');
var HttpConstants = Constants.HttpConstants;
var HeaderConstants = Constants.HeaderConstants;

// Expose 'BatchServiceClient'.
exports = module.exports = BatchServiceClient;

// Module constants.
BatchServiceClient.BATCH_CODE = -1;

/**
* Creates a new BatchServiceClient.
*
* Implements a batch service client able to produce OData batch requests from an array of operations.
* For more information about OData batch processing refer to http://www.odata.org/developers/protocols/batch.
*
* @constructor
*/
function BatchServiceClient(host, storageAccount, storageAccessKey, authenticationProvider) {
  BatchServiceClient.super_.call(this, host, storageAccount, storageAccessKey, authenticationProvider);

  this.operations = null;
}

util.inherits(BatchServiceClient, StorageServiceClient);

/**
* Begins a new batch scope.
*
* @return {undefined}
*/
BatchServiceClient.prototype.beginBatch = function () {
  this.operations = [];
};

/**
* Determines if there is a current batch.
*
* @return {boolean} Boolean value indicating if service is in a batch context or not.
*/
BatchServiceClient.prototype.isInBatch = function () {
  return this.operations !== null;
};

/**
* Terminates the current batch scope by clearing current operations.
*
* @return {undefined}
*/
BatchServiceClient.prototype.rollback = function () {
  this.operations = null;
};

/**
* Returns a boolean value indicating weather there are operations queued up for execution in a batch or not.
*
* @return {Boolean} True if there are operations queued up; false otherwise.
*/
BatchServiceClient.prototype.hasOperations = function () {
  return this.isInBatch() &&
         this.operations.length > 0;
};

/**
* Adds an operation to the batch.
*
* @param {object}  webResource The request parameters.
* @param {object}  outputData  The body for the operation.
* @return {undefined}
*/
BatchServiceClient.prototype.addOperation = function (webResource, outputData) {
  if (azureutil.objectIsNull(outputData)) {
    outputData = '';
  }

  if (webResource.httpVerb !== 'GET') {
    webResource.headers[HeaderConstants.CONTENT_ID] = this.operations.length + 1;

    if (webResource.httpVerb !== 'DELETE') {
      webResource.headers[HeaderConstants.CONTENT_TYPE] = 'application/atom+xml;type=entry';
    } else {
      delete webResource.headers[HeaderConstants.CONTENT_TYPE];
    }

    webResource.headers[HeaderConstants.CONTENT_LENGTH] = outputData.length;
  }

  this._setRequestUrl(webResource);
  var operation = {
    webResource: webResource
  };

  operation.content = webResource.httpVerb + ' ' + webResource.requestUrl + ' HTTP/1.1\n';

  for (var header in webResource.headers) {
    operation.content += header + ': ' + webResource.headers[header] + '\n';
  }

  operation.content += '\n';
  operation.content += outputData;

  this.operations.push(operation);
};

/**
* Commits the operations within the batch scope.
*
* @param {object}      [options]                        The request options.
* @param {int}         [options.timeoutIntervalInMs]    The timeout interval, in milliseconds, to use for the request.
* @param {function}    callback                         The response callback function.
* @return {undefined}
*/
BatchServiceClient.prototype.commitBatch = function (optionsOrCallback, callback) {
  var options = null;
  if (typeof optionsOrCallback === 'function' && !callback) {
    callback = optionsOrCallback;
  }
  else {
    options = optionsOrCallback;
  }

  if (!this.operations ||
    this.operations.length <= 0) {
    throw new Error('Nothing to commit');
  }

  var batchBoundary = 'batch_' + crypto.createHash('md5').update("" + (new Date()).getTime()).digest("hex");
  var changesetBoundary = 'changeset_' + crypto.createHash('md5').update("" + (new Date()).getTime()).digest("hex");

  var body = '--' + batchBoundary + '\n';
  body += HeaderConstants.CONTENT_TYPE + ': multipart/mixed; boundary=' + changesetBoundary + '\n\n';

  for (var operation in this.operations) {
    body += '--' + changesetBoundary + '\n';
    body += HeaderConstants.CONTENT_TYPE + ': application/http\n';
    body += HeaderConstants.CONTENT_TRANSFER_ENCODING_HEADER + ': binary\n\n';
    body += this.operations[operation].content + '\n';
  }

  body += '--' + changesetBoundary + '--\n';
  body += '--' + batchBoundary + '--';

  var webResource = WebResource.post('$batch')
    .withOkCode(HttpConstants.HttpResponseCodes.ACCEPTED_CODE)
    .withRawResponse(true);

  webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, 'multipart/mixed; boundary=' + batchBoundary);
  webResource.addOptionalHeader(HeaderConstants.DATA_SERVICE_VERSION, '1.0;NetFx');
  webResource.addOptionalHeader(HeaderConstants.MAX_DATA_SERVICE_VERSION, '2.0;NetFx');
  webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, body.length);

  var self = this;

  // Store current operations to process response
  // and clear batch operation to end isInBatch state after commiting
  var requestOperations = this.operations;
  this.operations = null;

  var processResponseCallback = function (responseObject, next) {
    var responseObjects = self.processResponse(responseObject, requestOperations);
    responseObject.operationResponses = responseObjects;

    var finalCallback = function (returnObject) {
      // perform final callback
      callback(returnObject.error, returnObject.operationResponses, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, body, options, processResponseCallback);
};

/**
* Processes a batch response.
*
* @param {object} responseObject The response object for the batch request.
* @return {array} An array with the processed / parsed responses.
*/
BatchServiceClient.prototype.processResponse = function (responseObject, requestOperations) {
  var responses = null;
  if (responseObject && responseObject.response && responseObject.response.body) {
    responses = [];
    var rawResponses = responseObject.response.body.split(Constants.CHANGESET_DELIMITER);

    var validResponse = 0;
    for (var i in rawResponses) {
      // Find HTTP/1.1 CODE line
      var httpLocation = rawResponses[i].indexOf('HTTP/');
      if (httpLocation !== -1) {
        var rawResponse = rawResponses[i].substring(httpLocation);
        // valid response
        var response = this.processOperation(requestOperations[validResponse++].webResource, rawResponse);
        responses.push(response);
      }
    }
  }

  return responses;
};

/**
* Processes a partial response.
*
* @param {WebResource} webResource The web resource for the response.
* @param {string}      rawResponse The raw, unparsed, http response from the server for the batch response.
* @return {object} A response object.
*/
BatchServiceClient.prototype.processOperation = function (webResource, rawResponse) {
  var responseObject = {
    error: null,
    response: { }
  };

  // Retrieve response code
  var firstSpace = rawResponse.indexOf(' ');
  responseObject.response.statusCode = parseInt(rawResponse.substring(firstSpace + 1, rawResponse.indexOf(' ', firstSpace + 2)), 10);
  responseObject.response.isSuccessful = webResource.validResponse(responseObject.response.statusCode);

  // Skip that line
  rawResponse = rawResponse.substring(rawResponse.indexOf('\n'));

  // Split into multiple lines and process them
  var responseLines = rawResponse.split('\r\n');

  // Populate headers
  responseObject.response.headers = { };
  responseObject.response.body = '';

  var isBody = false;
  for (var i in responseLines) {
    var line = responseLines[i];

    if (line === '' && !isBody) {
      isBody = true;
    } else if (isBody) {
      responseObject.response.body += line;
    } else {
      var headerSplit = line.indexOf(':');
      if (headerSplit !== -1) {
        responseObject.response.headers[line.substring(0, headerSplit).trim()] = line.substring(headerSplit + 1).trim();
      }
    }
  }

  this._parseResponse(responseObject.response);
  if (!responseObject.response.isSuccessful) {
    responseObject.error = this._normalizeError(responseObject.response.body);
  }

  return responseObject;
};<commit_msg>Safe array enumeration by using forEach method. Enumerating all object properties is error prone and may pick up properties that are not array indexes.<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Module dependencies.
var util = require('util');
var crypto = require('crypto');

var azureutil = require('../../util/util');
var ServiceClient = require('../core/serviceclient');
var StorageServiceClient = require('../core/storageserviceclient');
var WebResource = require('../../http/webresource');
var Constants = require('../../util/constants');
var HttpConstants = Constants.HttpConstants;
var HeaderConstants = Constants.HeaderConstants;

// Expose 'BatchServiceClient'.
exports = module.exports = BatchServiceClient;

// Module constants.
BatchServiceClient.BATCH_CODE = -1;

/**
* Creates a new BatchServiceClient.
*
* Implements a batch service client able to produce OData batch requests from an array of operations.
* For more information about OData batch processing refer to http://www.odata.org/developers/protocols/batch.
*
* @constructor
*/
function BatchServiceClient(host, storageAccount, storageAccessKey, authenticationProvider) {
  BatchServiceClient.super_.call(this, host, storageAccount, storageAccessKey, authenticationProvider);

  this.operations = null;
}

util.inherits(BatchServiceClient, StorageServiceClient);

/**
* Begins a new batch scope.
*
* @return {undefined}
*/
BatchServiceClient.prototype.beginBatch = function () {
  this.operations = [];
};

/**
* Determines if there is a current batch.
*
* @return {boolean} Boolean value indicating if service is in a batch context or not.
*/
BatchServiceClient.prototype.isInBatch = function () {
  return this.operations !== null;
};

/**
* Terminates the current batch scope by clearing current operations.
*
* @return {undefined}
*/
BatchServiceClient.prototype.rollback = function () {
  this.operations = null;
};

/**
* Returns a boolean value indicating weather there are operations queued up for execution in a batch or not.
*
* @return {Boolean} True if there are operations queued up; false otherwise.
*/
BatchServiceClient.prototype.hasOperations = function () {
  return this.isInBatch() &&
         this.operations.length > 0;
};

/**
* Adds an operation to the batch.
*
* @param {object}  webResource The request parameters.
* @param {object}  outputData  The body for the operation.
* @return {undefined}
*/
BatchServiceClient.prototype.addOperation = function (webResource, outputData) {
  if (azureutil.objectIsNull(outputData)) {
    outputData = '';
  }

  if (webResource.httpVerb !== 'GET') {
    webResource.headers[HeaderConstants.CONTENT_ID] = this.operations.length + 1;

    if (webResource.httpVerb !== 'DELETE') {
      webResource.headers[HeaderConstants.CONTENT_TYPE] = 'application/atom+xml;type=entry';
    } else {
      delete webResource.headers[HeaderConstants.CONTENT_TYPE];
    }

    webResource.headers[HeaderConstants.CONTENT_LENGTH] = outputData.length;
  }

  this._setRequestUrl(webResource);
  var operation = {
    webResource: webResource
  };

  operation.content = webResource.httpVerb + ' ' + webResource.requestUrl + ' HTTP/1.1\n';

  for (var header in webResource.headers) {
    operation.content += header + ': ' + webResource.headers[header] + '\n';
  }

  operation.content += '\n';
  operation.content += outputData;

  this.operations.push(operation);
};

/**
* Commits the operations within the batch scope.
*
* @param {object}      [options]                        The request options.
* @param {int}         [options.timeoutIntervalInMs]    The timeout interval, in milliseconds, to use for the request.
* @param {function}    callback                         The response callback function.
* @return {undefined}
*/
BatchServiceClient.prototype.commitBatch = function (optionsOrCallback, callback) {
  var options = null;
  if (typeof optionsOrCallback === 'function' && !callback) {
    callback = optionsOrCallback;
  }
  else {
    options = optionsOrCallback;
  }

  if (!this.operations ||
    this.operations.length <= 0) {
    throw new Error('Nothing to commit');
  }

  var batchBoundary = 'batch_' + crypto.createHash('md5').update("" + (new Date()).getTime()).digest("hex");
  var changesetBoundary = 'changeset_' + crypto.createHash('md5').update("" + (new Date()).getTime()).digest("hex");

  var body = '--' + batchBoundary + '\n';
  body += HeaderConstants.CONTENT_TYPE + ': multipart/mixed; boundary=' + changesetBoundary + '\n\n';

  for (var operation in this.operations) {
    body += '--' + changesetBoundary + '\n';
    body += HeaderConstants.CONTENT_TYPE + ': application/http\n';
    body += HeaderConstants.CONTENT_TRANSFER_ENCODING_HEADER + ': binary\n\n';
    body += this.operations[operation].content + '\n';
  }

  body += '--' + changesetBoundary + '--\n';
  body += '--' + batchBoundary + '--';

  var webResource = WebResource.post('$batch')
    .withOkCode(HttpConstants.HttpResponseCodes.ACCEPTED_CODE)
    .withRawResponse(true);

  webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, 'multipart/mixed; boundary=' + batchBoundary);
  webResource.addOptionalHeader(HeaderConstants.DATA_SERVICE_VERSION, '1.0;NetFx');
  webResource.addOptionalHeader(HeaderConstants.MAX_DATA_SERVICE_VERSION, '2.0;NetFx');
  webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, body.length);

  var self = this;

  // Store current operations to process response
  // and clear batch operation to end isInBatch state after commiting
  var requestOperations = this.operations;
  this.operations = null;

  var processResponseCallback = function (responseObject, next) {
    var responseObjects = self.processResponse(responseObject, requestOperations);
    responseObject.operationResponses = responseObjects;

    var finalCallback = function (returnObject) {
      // perform final callback
      callback(returnObject.error, returnObject.operationResponses, returnObject.response);
    };

    next(responseObject, finalCallback);
  };

  this.performRequest(webResource, body, options, processResponseCallback);
};

/**
* Processes a batch response.
*
* @param {object} responseObject The response object for the batch request.
* @return {array} An array with the processed / parsed responses.
*/
BatchServiceClient.prototype.processResponse = function (responseObject, requestOperations) {
  var self = this;
  var responses = null;
  if (responseObject && responseObject.response && responseObject.response.body) {
    responses = [];
    var rawResponses = responseObject.response.body.split(Constants.CHANGESET_DELIMITER);

    var validResponse = 0;
    rawResponses.forEach(function(rawResponse) {
      // Find HTTP/1.1 CODE line
      var httpLocation = rawResponse.indexOf('HTTP/');
      if (httpLocation !== -1) {
        rawResponse = rawResponse.substring(httpLocation);
        // valid response
        var response = self.processOperation(requestOperations[validResponse++].webResource, rawResponse);
        responses.push(response);
      }
    });
  }

  return responses;
};

/**
* Processes a partial response.
*
* @param {WebResource} webResource The web resource for the response.
* @param {string}      rawResponse The raw, unparsed, http response from the server for the batch response.
* @return {object} A response object.
*/
BatchServiceClient.prototype.processOperation = function (webResource, rawResponse) {
  var responseObject = {
    error: null,
    response: { }
  };

  // Retrieve response code
  var firstSpace = rawResponse.indexOf(' ');
  responseObject.response.statusCode = parseInt(rawResponse.substring(firstSpace + 1, rawResponse.indexOf(' ', firstSpace + 2)), 10);
  responseObject.response.isSuccessful = webResource.validResponse(responseObject.response.statusCode);

  // Skip that line
  rawResponse = rawResponse.substring(rawResponse.indexOf('\n'));

  // Split into multiple lines and process them
  var responseLines = rawResponse.split('\r\n');

  // Populate headers
  responseObject.response.headers = { };
  responseObject.response.body = '';

  var isBody = false;
  responseLines.forEach(function(line) {
    if (line === '' && !isBody) {
      isBody = true;
    } else if (isBody) {
      responseObject.response.body += line;
    } else {
      var headerSplit = line.indexOf(':');
      if (headerSplit !== -1) {
        responseObject.response.headers[line.substring(0, headerSplit).trim()] = line.substring(headerSplit + 1).trim();
      }
    }
  });

  this._parseResponse(responseObject.response);
  if (!responseObject.response.isSuccessful) {
    responseObject.error = this._normalizeError(responseObject.response.body);
  }

  return responseObject;
};<|endoftext|>


<commit_before>
module.exports = request
request.GET = GET
request.PUT = PUT
request.reg = reg
request.upload = upload

var npm = require("../../../npm")
  , http
  , https
  , url = require("url")
  , log = require("../log")
  , ini = require("../ini")
  , fs = require("graceful-fs")
  , rm = require("../rm-rf")
  , asyncMap = require("slide").asyncMap
  , proxyify = require("../proxyify")
  , warnedAuth = false
  , newloctimeout = 0

function request (method, where, what, etag, nofollow, cb_) {
  log.verbose(where||"/", method)

  if (where.match(/^\/?favicon.ico/)) {
    return cb_(new Error("favicon.ico isn't a package, it's a picture."))
  }
  if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false
  if (typeof cb_ !== "function") cb_ = etag, etag = null
  if (typeof cb_ !== "function") cb_ = what, what = null
  var registry = reg()
  if (registry instanceof Error) return cb_(registry)

  // Since there are multiple places where an error could occur,
  // don't let the cb be called more than once.
  var errState = null
  function cb (er) {
    if (errState) return
    if (er) errState = er
    cb_.apply(null, arguments)
  }

  var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/
    , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/
    , authRequired = (what || npm.config.get("always-auth"))
                      && !where.match(adduserNew)
                   || where.match(adduserChange)
                   || method === "DELETE"
  if (!where.match(/^https?:\/\//)) {
    log.verbose(where, "raw, before any munging")

    var q = where.split("?")
    where = q.shift()
    q = q.join("?")

    if (where.charAt(0) !== "/") where = "/" + where
    where = "." + where.split("/").map(function (p) {
      p = p.trim()
      if (p.match(/^org.couchdb.user/)) {
        return p.replace(/\//g, encodeURIComponent("/"))
      }
      return encodeURIComponent(p)
    }).join("/")
    if (q) where += "?" + q
    log.verbose([registry, where], "url resolving")
    where = url.resolve(registry, where)
    log.verbose(where, "url resolved")
  } else {
    log.verbose(where, "no need to resolve")
  }

  var remote = url.parse(where)
    , secure = remote.protocol === "https:"
    , port = remote.port || (secure ? 443 : 80)
    , hostname = remote.hostname
    , auth = authRequired && npm.config.get("_auth")

  log.verbose(remote, "url parsed")
  if (port !== (secure ? 443 : 80)) hostname += ":" + port

  if (authRequired && !auth) {
    return cb(new Error(
      "Cannot insert data into the registry without authorization\n"
      + "See: npm-adduser(1)"))
  }

  if (auth && !secure && !warnedAuth) {
    warnedAuth = true
    log.warn("Sending authorization over insecure channel.")
  }

  var headers = { "accept" : "application/json" }
  if (auth) headers.authorization = "Basic " + auth
  if (what) {
    if (what instanceof File) {
      log.verbose(what.name, "uploading")
      headers["content-type"] = "application/octet-stream"
    } else {
      delete what._etag
      log.silly(what,"writing json")
      what = new Buffer(JSON.stringify(what))
      headers["content-type"] = "application/json"
    }
    headers["content-length"] = what.length
  } else {
    headers["content-length"] = 0
  }

  if (etag) {
    log.verbose(etag, "etag")
    headers[method === "GET" ? "if-none-match" : "if-match"] = etag
  }

  headers.host = hostname

  if (!remote.protocol) log.warn(remote, "No protocol?")

  var opts = { method: method
             , headers: headers
             , path: (remote.pathname||"/")
                   + (remote.search||"")
                   + (remote.hash||"")
             , host: remote.hostname
             , secure: remote.protocol
                       && remote.protocol.toLowerCase() === "https:"
             , port: remote.port
             }
  if (!opts.port) opts.port = opts.secure ? 443 : 80
  if (opts.port !== (opts.secure ? 443 : 80)) {
    opts.headers.host = opts.headers.host || opts.host
    opts.headers.host += ":" + opts.port
  }

  opts = proxyify(npm.config.get("proxy"), remote, opts)
  if (!opts) return cb(new Error("Bad proxy config: "+npm.config.get("proxy")))

  if (opts.secure) https = https || require("https")
  else http = http || require("http")

  var req = (opts.secure ? https : http).request(opts, function (response) {
    log.verbose(where, "response")

    var data = ""
    response.on("error", log.er(cb, "response error from "+where))
    response.on("data", function (chunk) {
      log.silly(chunk+"", "chunk")
      data += chunk
    })

    response.on("end", function () {
      if (!nofollow
          && (response.statusCode === 301 || response.statusCode === 302)) {
        // relative redirects SHOULD be disallowed, but alas...
        var newLoc = response.headers.location
        newLoc = newLoc && url.resolve(where, newLoc)
        log.verbose(newLoc, "redirect")
        if (!newLoc) return cb(new Error(
          response.statusCode + " status code with no location"))
        //FIXME: wtf? why does this timeout make it work?
        return setTimeout(function () {
          log.verbose(newLoc, "redirect fer reals")
          request(method, newLoc, what, etag,
                       log.er(cb, "Failed to fetch "+newLoc))
        }, 1000*(newloctimeout ++))
      }

      var parsed
      if (response.statusCode !== 304) {
        try {
          parsed = JSON.parse(data)
        } catch (ex) {
          ex.message += "\n" + data
          log.verbose(data, "bad json")
          log.error("error parsing json", "registry")
          return cb(ex, null, data, response)
        }
      }

      var er = null
      if (parsed && response.headers.etag) {
        parsed._etag = response.headers.etag
      }

      if (parsed && parsed.error) {
        var w = url.parse(where).pathname.substr(1)
        if (parsed.error === "not_found") {
          w = w.split("/")
          name = w[w.indexOf("_rewrite") + 1]
          er = new Error("404 Not Found: "+name)
          er.errno = npm.E404
          er.pkgid = name
        } else {
          er = new Error(
            parsed.error + " " + (parsed.reason || "") + ": " + w)
        }
      } else if (method !== "HEAD" && method !== "GET") {

        // invalidate cache
        // This is irrelevant for commands that do etag caching, but
        // ls and view also have a timed cache, so this keeps the user
        // from thinking that it didn't work when it did.
        // Note that failure is an acceptable option here, since the
        // only result will be a stale cache for some helper commands.
        var path = require("path")
          , p = url.parse(where).pathname.split("/")
          , _ = "/"
          , caches = p.map(function (part) {
              return _ = path.join(_, part)
            }).map(function (cache) {
              return path.join(npm.cache, cache, ".cache.json")
            })

        // if the method is DELETE, then also remove the thing itself.
        // Note that the search index is probably invalid.  Whatever.
        // That's what you get for deleting stuff.  Don't do that.
        if (method === "DELETE") {
          p = p.slice(0, p.indexOf("-rev"))
          caches.push(path.join(npm.cache, p.join("/")))
        }

        asyncMap(caches, rm, function () {})
      }
      return cb(er, parsed, data, response)
    })
  }).on("error", cb)

  if (what instanceof File) {
    var size = Math.min(what.length, 1024*1024*1024)
      , remaining = what.length
    log.verbose(what.length, "bytes")
    ;(function W () {
      var b = new Buffer(size)
      try {
        var bytesRead = fs.readSync(what.fd, b, 0, b.length, null)
      } catch (er) {
        return log.er(cb, "Failure to read tarball")(er)
      }
      remaining -= bytesRead
      if (bytesRead) {
        log(bytesRead, "read")
        log(remaining, "remain")
        return (
            req.write(bytesRead === b.length ? b : b.slice(0, bytesRead))
          ) ? W()
            : req.on("drain", function DRAIN () {
                log.silly(remaining, "drain")
                req.removeListener("drain", DRAIN)
                W()
              })
      }
      if (!remaining) {
        req.end()
        log.verbose(what.name, "written to uploading stream")
        log.verbose("Not done yet! If it hangs/quits now, it didn't work.", "upload")
        return
      }
      // wtf!? No bytes read, but also bytes remaining.
      return cb(new Error("Some kind of weirdness reading the file"))
    })()
    return

  } else if (typeof what === "string" || Buffer.isBuffer(what)) {
    // just a json blob
    req.write(what)
  }

  req.end()
}

function GET (where, etag, nofollow, cb) {
  request("GET", where, null, etag, nofollow, cb)
}

function PUT (where, what, etag, nofollow, cb) {
  request("PUT", where, what, etag, nofollow, cb)
}

function upload (where, filename, etag, nofollow, cb) {
  if (typeof nofollow === "function") cb = nofollow, nofollow = false
  if (typeof etag === "function") cb = etag, etag = null

  new File(filename, function (er, f) {
    if (er) return log.er(cb, "Couldn't open "+filename)(er)
    PUT(where, f, etag, nofollow, function (er) {
      log.info("done with upload", "publish")
      cb(er)
    })
  })
}

function File (name, cb) {
  var f = this
  f.name = name
  if (f.loaded) return cb(null, f)
  log.info(f.name, "stat")
  fs.stat(f.name, function (er, stat) {
    if (er) return log.er(cb, "doesn't exist "+f.name)(er)
    log.silly(stat, "stat "+name)
    f.length = stat.size
    fs.open(f.name, "r", 0666, function (er, fd) {
      if (er) return log.er(cb, "Error opening "+f.name)(er)
      f.fd = fd
      cb(null, f)
    })
  })
}

function reg () {
  var r = npm.config.get("registry")
  if (!r) {
    return new Error("Must define registry URL before accessing registry.")
  }
  if (r.substr(-1) !== "/") r += "/"
  npm.config.set("registry", r)
  return r
}
<commit_msg>Setting the host there is the wrong thing to do<commit_after>
module.exports = request
request.GET = GET
request.PUT = PUT
request.reg = reg
request.upload = upload

var npm = require("../../../npm")
  , http
  , https
  , url = require("url")
  , log = require("../log")
  , ini = require("../ini")
  , fs = require("graceful-fs")
  , rm = require("../rm-rf")
  , asyncMap = require("slide").asyncMap
  , proxyify = require("../proxyify")
  , warnedAuth = false
  , newloctimeout = 0

function request (method, where, what, etag, nofollow, cb_) {
  log.verbose(where||"/", method)

  if (where.match(/^\/?favicon.ico/)) {
    return cb_(new Error("favicon.ico isn't a package, it's a picture."))
  }
  if (typeof cb_ !== "function") cb_ = nofollow, nofollow = false
  if (typeof cb_ !== "function") cb_ = etag, etag = null
  if (typeof cb_ !== "function") cb_ = what, what = null
  var registry = reg()
  if (registry instanceof Error) return cb_(registry)

  // Since there are multiple places where an error could occur,
  // don't let the cb be called more than once.
  var errState = null
  function cb (er) {
    if (errState) return
    if (er) errState = er
    cb_.apply(null, arguments)
  }

  var adduserChange = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)\/-rev/
    , adduserNew = /^\/?-\/user\/org\.couchdb\.user:([^\/]+)/
    , authRequired = (what || npm.config.get("always-auth"))
                      && !where.match(adduserNew)
                   || where.match(adduserChange)
                   || method === "DELETE"
  if (!where.match(/^https?:\/\//)) {
    log.verbose(where, "raw, before any munging")

    var q = where.split("?")
    where = q.shift()
    q = q.join("?")

    if (where.charAt(0) !== "/") where = "/" + where
    where = "." + where.split("/").map(function (p) {
      p = p.trim()
      if (p.match(/^org.couchdb.user/)) {
        return p.replace(/\//g, encodeURIComponent("/"))
      }
      return encodeURIComponent(p)
    }).join("/")
    if (q) where += "?" + q
    log.verbose([registry, where], "url resolving")
    where = url.resolve(registry, where)
    log.verbose(where, "url resolved")
  } else {
    log.verbose(where, "no need to resolve")
  }

  var remote = url.parse(where)
    , secure = remote.protocol === "https:"
    , port = remote.port || (secure ? 443 : 80)
    , hostname = remote.hostname
    , auth = authRequired && npm.config.get("_auth")

  log.verbose(remote, "url parsed")
  if (port !== (secure ? 443 : 80)) hostname += ":" + port

  if (authRequired && !auth) {
    return cb(new Error(
      "Cannot insert data into the registry without authorization\n"
      + "See: npm-adduser(1)"))
  }

  if (auth && !secure && !warnedAuth) {
    warnedAuth = true
    log.warn("Sending authorization over insecure channel.")
  }

  var headers = { "accept" : "application/json" }
  if (auth) headers.authorization = "Basic " + auth
  if (what) {
    if (what instanceof File) {
      log.verbose(what.name, "uploading")
      headers["content-type"] = "application/octet-stream"
    } else {
      delete what._etag
      log.silly(what,"writing json")
      what = new Buffer(JSON.stringify(what))
      headers["content-type"] = "application/json"
    }
    headers["content-length"] = what.length
  } else {
    headers["content-length"] = 0
  }

  if (etag) {
    log.verbose(etag, "etag")
    headers[method === "GET" ? "if-none-match" : "if-match"] = etag
  }

  if (!remote.protocol) log.warn(remote, "No protocol?")

  var opts = { method: method
             , headers: headers
             , path: (remote.pathname||"/")
                   + (remote.search||"")
                   + (remote.hash||"")
             , host: remote.hostname
             , secure: remote.protocol
                       && remote.protocol.toLowerCase() === "https:"
             , port: remote.port
             }
  if (!opts.port) opts.port = opts.secure ? 443 : 80
  if (opts.port !== (opts.secure ? 443 : 80)) {
    opts.headers.host = opts.headers.host || opts.host
    opts.headers.host += ":" + opts.port
  }
  log.verbose(headers.host, "headers.host")

  opts = proxyify(npm.config.get("proxy"), remote, opts)
  if (!opts) return cb(new Error("Bad proxy config: "+npm.config.get("proxy")))

  if (opts.secure) https = https || require("https")
  else http = http || require("http")

  var req = (opts.secure ? https : http).request(opts, function (response) {
    log.verbose(where, "response")

    var data = ""
    response.on("error", log.er(cb, "response error from "+where))
    response.on("data", function (chunk) {
      log.silly(chunk+"", "chunk")
      data += chunk
    })

    response.on("end", function () {
      if (!nofollow
          && (response.statusCode === 301 || response.statusCode === 302)) {
        // relative redirects SHOULD be disallowed, but alas...
        var newLoc = response.headers.location
        newLoc = newLoc && url.resolve(where, newLoc)
        log.verbose(newLoc, "redirect")
        if (!newLoc) return cb(new Error(
          response.statusCode + " status code with no location"))
        //FIXME: wtf? why does this timeout make it work?
        return setTimeout(function () {
          log.verbose(newLoc, "redirect fer reals")
          request(method, newLoc, what, etag,
                       log.er(cb, "Failed to fetch "+newLoc))
        }, 1000*(newloctimeout ++))
      }

      var parsed
      if (response.statusCode !== 304) {
        try {
          parsed = JSON.parse(data)
        } catch (ex) {
          ex.message += "\n" + data
          log.verbose(data, "bad json")
          log.error("error parsing json", "registry")
          return cb(ex, null, data, response)
        }
      }

      var er = null
      if (parsed && response.headers.etag) {
        parsed._etag = response.headers.etag
      }

      if (parsed && parsed.error) {
        var w = url.parse(where).pathname.substr(1)
        if (parsed.error === "not_found") {
          w = w.split("/")
          name = w[w.indexOf("_rewrite") + 1]
          er = new Error("404 Not Found: "+name)
          er.errno = npm.E404
          er.pkgid = name
        } else {
          er = new Error(
            parsed.error + " " + (parsed.reason || "") + ": " + w)
        }
      } else if (method !== "HEAD" && method !== "GET") {

        // invalidate cache
        // This is irrelevant for commands that do etag caching, but
        // ls and view also have a timed cache, so this keeps the user
        // from thinking that it didn't work when it did.
        // Note that failure is an acceptable option here, since the
        // only result will be a stale cache for some helper commands.
        var path = require("path")
          , p = url.parse(where).pathname.split("/")
          , _ = "/"
          , caches = p.map(function (part) {
              return _ = path.join(_, part)
            }).map(function (cache) {
              return path.join(npm.cache, cache, ".cache.json")
            })

        // if the method is DELETE, then also remove the thing itself.
        // Note that the search index is probably invalid.  Whatever.
        // That's what you get for deleting stuff.  Don't do that.
        if (method === "DELETE") {
          p = p.slice(0, p.indexOf("-rev"))
          caches.push(path.join(npm.cache, p.join("/")))
        }

        asyncMap(caches, rm, function () {})
      }
      return cb(er, parsed, data, response)
    })
  }).on("error", cb)

  if (what instanceof File) {
    var size = Math.min(what.length, 1024*1024*1024)
      , remaining = what.length
    log.verbose(what.length, "bytes")
    ;(function W () {
      var b = new Buffer(size)
      try {
        var bytesRead = fs.readSync(what.fd, b, 0, b.length, null)
      } catch (er) {
        return log.er(cb, "Failure to read tarball")(er)
      }
      remaining -= bytesRead
      if (bytesRead) {
        log(bytesRead, "read")
        log(remaining, "remain")
        return (
            req.write(bytesRead === b.length ? b : b.slice(0, bytesRead))
          ) ? W()
            : req.on("drain", function DRAIN () {
                log.silly(remaining, "drain")
                req.removeListener("drain", DRAIN)
                W()
              })
      }
      if (!remaining) {
        req.end()
        log.verbose(what.name, "written to uploading stream")
        log.verbose("Not done yet! If it hangs/quits now, it didn't work.", "upload")
        return
      }
      // wtf!? No bytes read, but also bytes remaining.
      return cb(new Error("Some kind of weirdness reading the file"))
    })()
    return

  } else if (typeof what === "string" || Buffer.isBuffer(what)) {
    // just a json blob
    req.write(what)
  }

  req.end()
}

function GET (where, etag, nofollow, cb) {
  request("GET", where, null, etag, nofollow, cb)
}

function PUT (where, what, etag, nofollow, cb) {
  request("PUT", where, what, etag, nofollow, cb)
}

function upload (where, filename, etag, nofollow, cb) {
  if (typeof nofollow === "function") cb = nofollow, nofollow = false
  if (typeof etag === "function") cb = etag, etag = null

  new File(filename, function (er, f) {
    if (er) return log.er(cb, "Couldn't open "+filename)(er)
    PUT(where, f, etag, nofollow, function (er) {
      log.info("done with upload", "publish")
      cb(er)
    })
  })
}

function File (name, cb) {
  var f = this
  f.name = name
  if (f.loaded) return cb(null, f)
  log.info(f.name, "stat")
  fs.stat(f.name, function (er, stat) {
    if (er) return log.er(cb, "doesn't exist "+f.name)(er)
    log.silly(stat, "stat "+name)
    f.length = stat.size
    fs.open(f.name, "r", 0666, function (er, fd) {
      if (er) return log.er(cb, "Error opening "+f.name)(er)
      f.fd = fd
      cb(null, f)
    })
  })
}

function reg () {
  var r = npm.config.get("registry")
  if (!r) {
    return new Error("Must define registry URL before accessing registry.")
  }
  if (r.substr(-1) !== "/") r += "/"
  npm.config.set("registry", r)
  return r
}
<|endoftext|>


<commit_before>/* Load Lively */

var loadCallbacks = []
export function whenLoaded(cb) {
    loadCallbacks.push(cb)
}

function loadJavaScriptThroughDOM(name, src, force) {
  return new Promise(function (resolve) {
    var scriptNode = document.querySelector(name);
    if (scriptNode) {
      scriptNode.remove();
    }
    var script = document.createElement("script");
    script.id = name;
    script.charset = "utf-8";
    script.type = "text/javascript";
    if (force) {
      src += +"?" + Date.now();
    }
    script.src = src;
    script.onload = function () {
      resolve();
    };
    document.head.appendChild(script);
  });
}

if ('serviceWorker' in navigator) {
  console.log("LOAD Lively4: boot lively4 service worker")
  var root = "" + lively4url + "/";
  var serviceworkerReady = false

  var onReady = function() {
    serviceworkerReady = true;
    // Lively has all the dependencies

    Promise.resolve("")
      .then( function() {
        return loadJavaScriptThroughDOM("livelyModules",
          lively4url + "/src/external/lively.modules-with-lively.vm.js")})
      .then( function() {
        console.log("lively.modules loaded... now try to load lively4");
        return lively.modules.importPackage(lively4url)})
      .then(function(module) {
        lively.initializeHalos();
        // lively.initializeSearch();
        lively.components.loadUnresolved();
        console.log("running on load callbacks:");
        loadCallbacks.forEach(function(cb){
            try {
               cb()
            } catch(e) {
                console.log("Error running on load callback: "  + cb + " error: " + e)
            }
        });
        if (!window.__karma__) {
          window.onbeforeunload = function(e) {
            return 'Do you really want to leave this page?';
          };
        }
        console.log("lively loaded");
      })
  }

  if (navigator.serviceWorker.controller) {
    console.log("Use existing service worker")
    // we don't have to do anything here... the service worker is already there
    onReady()
  } else {
    navigator.serviceWorker.register(new URL('swx-loader.js', window.location)).then(function(registration) {
        // Registration was successful
        console.log('ServiceWorker registration successful with scope: ', registration.scope);

        // so now we have to reload!
        console.log("ok... lets WAIT WAIT WAIT!!!!")
        // console.log("Lively4 ServiceWorker installed! Reboot needed! ;-)")
        // window.location = window.location
    }).catch(function(err) {
        // registration failed
        console.log('ServiceWorker registration failed: ', err);
    });
    navigator.serviceWorker.ready.then(onReady)
  }

  var fs = new Promise(function(resolve, reject) {
      navigator.webkitPersistentStorage.requestQuota(1024 * 1024 * 10, function(grantedQuota) {
          self.webkitRequestFileSystem(PERSISTENT, grantedQuota, resolve, reject)
      }, reject)
  })

  navigator.serviceWorker.addEventListener("message", (event) => {
    var reject = function(err) {
        event.ports[0].postMessage({error: err})
    }

    switch(event.data.name) {
      case 'swx:readFile':
          fs.then((fs) => {
              fs.root.getFile(event.data.file, undefined, function(fileEntry) {
                  fileEntry.file(function(file) {
                      var reader = new FileReader()
                      reader.onloadend = function(e) {
                          console.log('[LFS] read complete', e)
                          event.ports[0].postMessage({content: reader.result})
                      }
                      reader.readAsText(file)
                  })
              }, reject)
          }).catch(reject)
          break
        case 'swx:writeFile':
          fs.then((fs) => {
              fs.root.getFile(event.data.file, {create: true, exclusive: false}, function(file) {
                  file.createWriter(function (writer) {
                      writer.onwriteend = function(e) {
                          console.log("[LFS] write complete", e)

                          event.ports[0].postMessage({
                              content: event.data.content
                          })
                      }
                      writer.onerror = reject
                      writer.write(new Blob([event.data.content], {type: 'text/plain'}))
                  }, reject)
              }, reject)
          }).catch(reject)

          break
    }
  })
}

document.addEventListener('DOMContentLoaded', function () {
  if (Notification.permission !== "granted")
    Notification.requestPermission();
});

<commit_msg>SYNC  M src/client/load.js; M src/external/lively4-search;<commit_after>/* Load Lively */

var loadCallbacks = []
export function whenLoaded(cb) {
    loadCallbacks.push(cb)
}

function loadJavaScriptThroughDOM(name, src, force) {
  return new Promise(function (resolve) {
    var scriptNode = document.querySelector(name);
    if (scriptNode) {
      scriptNode.remove();
    }
    var script = document.createElement("script");
    script.id = name;
    script.charset = "utf-8";
    script.type = "text/javascript";
    if (force) {
      src += +"?" + Date.now();
    }
    script.src = src;
    script.onload = function () {
      resolve();
    };
    document.head.appendChild(script);
  });
}

if ('serviceWorker' in navigator) {
  console.log("LOAD Lively4: boot lively4 service worker")
  var root = "" + lively4url + "/";
  var serviceworkerReady = false

  var onReady = function() {
    serviceworkerReady = true;
    // Lively has all the dependencies

    Promise.resolve("")
      .then( function() {
        return loadJavaScriptThroughDOM("livelyModules",
          lively4url + "/src/external/lively.modules-with-lively.vm.js")})
      .then( function() {
        console.log("lively.modules loaded... now try to load lively4");
        return lively.modules.importPackage(lively4url)})
      .then(function(module) {
        lively.initializeHalos();
        lively.initializeSearch();
        lively.components.loadUnresolved();
        console.log("running on load callbacks:");
        loadCallbacks.forEach(function(cb){
            try {
               cb()
            } catch(e) {
                console.log("Error running on load callback: "  + cb + " error: " + e)
            }
        });
        if (!window.__karma__) {
          window.onbeforeunload = function(e) {
            return 'Do you really want to leave this page?';
          };
        }
        console.log("lively loaded");
      })
  }

  if (navigator.serviceWorker.controller) {
    console.log("Use existing service worker")
    // we don't have to do anything here... the service worker is already there
    onReady()
  } else {
    navigator.serviceWorker.register(new URL('swx-loader.js', window.location)).then(function(registration) {
        // Registration was successful
        console.log('ServiceWorker registration successful with scope: ', registration.scope);

        // so now we have to reload!
        console.log("ok... lets WAIT WAIT WAIT!!!!")
        // console.log("Lively4 ServiceWorker installed! Reboot needed! ;-)")
        // window.location = window.location
    }).catch(function(err) {
        // registration failed
        console.log('ServiceWorker registration failed: ', err);
    });
    navigator.serviceWorker.ready.then(onReady)
  }

  var fs = new Promise(function(resolve, reject) {
      navigator.webkitPersistentStorage.requestQuota(1024 * 1024 * 10, function(grantedQuota) {
          self.webkitRequestFileSystem(PERSISTENT, grantedQuota, resolve, reject)
      }, reject)
  })

  navigator.serviceWorker.addEventListener("message", (event) => {
    var reject = function(err) {
        event.ports[0].postMessage({error: err})
    }

    switch(event.data.name) {
      case 'swx:readFile':
          fs.then((fs) => {
              fs.root.getFile(event.data.file, undefined, function(fileEntry) {
                  fileEntry.file(function(file) {
                      var reader = new FileReader()
                      reader.onloadend = function(e) {
                          console.log('[LFS] read complete', e)
                          event.ports[0].postMessage({content: reader.result})
                      }
                      reader.readAsText(file)
                  })
              }, reject)
          }).catch(reject)
          break
        case 'swx:writeFile':
          fs.then((fs) => {
              fs.root.getFile(event.data.file, {create: true, exclusive: false}, function(file) {
                  file.createWriter(function (writer) {
                      writer.onwriteend = function(e) {
                          console.log("[LFS] write complete", e)

                          event.ports[0].postMessage({
                              content: event.data.content
                          })
                      }
                      writer.onerror = reject
                      writer.write(new Blob([event.data.content], {type: 'text/plain'}))
                  }, reject)
              }, reject)
          }).catch(reject)

          break
    }
  })
}

document.addEventListener('DOMContentLoaded', function () {
  if (Notification.permission !== "granted")
    Notification.requestPermission();
});

<|endoftext|>


<commit_before>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	var idInt = 0;
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			idInt++;
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			var id = "collapsible-" + idInt;

			this.header.attr( "role", "button" );

			this.header.attr( "aria-haspopup", "true" );

			this.header.attr( "aria-controls", id );

			this.header.attr( "tabindex", "0" );

			this.content.attr( "role", "menu" );

			this.content.addClass( this.options.contentClass );
			this.content.id = id;
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				// tap can't be namespaced yet without special events api: https://github.com/filamentgroup/tappy/issues/22
				.bind( ( window.tappy ? "tap" : "click" ), function( e ){
					self.toggle();
					e.preventDefault();
				})
				.bind( "keyup." + pluginName, function( e ){
					if( e.which === 13 || e.which === 32 ){
						self.toggle();
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse();
			}
		},

		collapsed: false,

		expand: function () {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.content.attr( "aria-hidden", "false" );
			self.element.trigger( "expand" );
		},

		collapse: function() {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.content.attr( "aria-hidden", "true" );
			self.element.trigger( "collapse" );
		},

		toggle: function(){
			if(  this.collapsed ){
				this.expand();
			} else {
				this.collapse();
			}
		}
	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<commit_msg>when focused on a collapsible, space bar should toggle state without scrolling page.<commit_after>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	var idInt = 0;
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			idInt++;
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			var id = "collapsible-" + idInt;

			this.header.attr( "role", "button" );

			this.header.attr( "aria-haspopup", "true" );

			this.header.attr( "aria-controls", id );

			this.header.attr( "tabindex", "0" );

			this.content.attr( "role", "menu" );

			this.content.addClass( this.options.contentClass );
			this.content.id = id;
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				// tap can't be namespaced yet without special events api: https://github.com/filamentgroup/tappy/issues/22
				.bind( ( window.tappy ? "tap" : "click" ), function( e ){
					self.toggle();
					e.preventDefault();
				})
				.bind( "keydown." + pluginName, function( e ){
					if( e.which === 13 || e.which === 32 ){
						self.toggle();
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse();
			}
		},

		collapsed: false,

		expand: function () {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.content.attr( "aria-hidden", "false" );
			self.element.trigger( "expand" );
		},

		collapse: function() {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.content.attr( "aria-hidden", "true" );
			self.element.trigger( "collapse" );
		},

		toggle: function(){
			if(  this.collapsed ){
				this.expand();
			} else {
				this.collapse();
			}
		},

		focusNext: function(){

		}
	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<|endoftext|>


<commit_before>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	var idInt = 0;
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			idInt++;
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			var id = "collapsible-" + idInt;

			this.header.attr( "role", "button" );

			this.header.attr( "aria-haspopup", "true" );

			this.header.attr( "aria-controls", id );

			this.header.attr( "tabindex", "0" );

			this.content.attr( "tabindex", "0" );

			this.content.attr( "role", "menu" );

			this.content.addClass( this.options.contentClass );
			this.content.id = id;
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				// tap can't be namespaced yet without special events api: https://github.com/filamentgroup/tappy/issues/22
				.bind( ( window.tappy ? "tap" : "click" ), function( e ){
					self.toggle( e.target );
					e.preventDefault();
				});

			this.element
				.bind( "keydown." + pluginName, function( e ){
					if( ( e.which === 13 || e.which === 32 ) ){
						self.toggle( e.target );
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse( false );
			}
		},

		collapsed: false,

		expand: function ( target ) {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.content.attr( "aria-hidden", "false" );
			self.focusNext( target );
			self.element.trigger( "expand" );
		},

		collapse: function( target ) {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.content.attr( "aria-hidden", "true" );
			if( $( target ).closest( self.header ).length || $( target ).is( self.content ) ){
				self.focusHeader();
			}
			self.element.trigger( "collapse" );
		},

		toggle: function( target ){
			if(  this.collapsed ){
				this.expand( target );
			} else {
				this.collapse( target );
			}
		},

		focusHeader: function(){
			this.header.focus();
		},

		focusNext: function( target ){
			var $nextFocusable = $( target ).nextAll( "a, input, select, button, [tabindex]" ).first();
			if( $nextFocusable.length ){
				$nextFocusable[ 0 ].focus();
			}
		},

		focusPrev: function(){
			var $nextFocusable = $( target ).prevAll( "a, input, select, button, [tabindex]" ).first();
			if( $nextFocusable.length ){
				$nextFocusable[ 0 ].focus();
			}
		}
	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<commit_msg>don't throw focus around on toggle<commit_after>/*
 * Collapsible widget
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	// Defaults
	var pluginName = "collapsible";
	var idInt = 0;
	// overrideable defaults
	var defaults = {
		pluginClass: pluginName,
		collapsedClass: pluginName + "-collapsed",
		headerClass: pluginName + "-header",
		contentClass: pluginName + "-content",
		enhancedClass: pluginName + "-enhanced",
		instructions: false,
		collapsed: false
	};

	// plugin constructor
	function Plugin(element, options) {
		this.element = $( element );
		var self = this,
			dataOptions = {};

		// Allow data-attr option setting
		if( this.element.is( "[data-config]" ) ){
			for( var option in defaults ){
					if( defaults.hasOwnProperty( option) ){
					var value = self.element.attr( "data-" + option.replace( /[A-Z]/g, function( c ) {
									return "-" + c.toLowerCase();
								}));

					if ( value !== undefined ) {
						if( value === "true" || value === "false" ){
							dataOptions[ option ] = value === "true";
						}
						else {
							dataOptions[ option ] = value;
						}
					}
				}
			}
		}

		this.options = $.extend( {}, defaults, dataOptions, options );

		// allow the collapsedClass to set the option if specified
		if( this.element.is( "." + this.options.collapsedClass ) ){
			this.options.collapsed= true;
		}

		this._defaults = defaults;
		this._name = pluginName;
		this.init();
	}

	Plugin.prototype = {
		init: function () {
			this.header = this.element.children().eq( 0 );
			this.content = this.header.next();
			this._addAttributes();
			this._bindEvents();
			idInt++;
			this.element.trigger( "init" );
		},

		_addAttributes: function(){
			this.element
				.addClass( this.options.pluginClass )
				.addClass( this.options.enhancedClass );

			this.header.addClass( this.options.headerClass );

			if( this.options.instructions ){
				this.header.attr( "title", this.options.instructions );
			}

			var id = "collapsible-" + idInt;

			this.header.attr( "role", "button" );

			this.header.attr( "aria-haspopup", "true" );

			this.header.attr( "aria-controls", id );

			this.header.attr( "tabindex", "0" );

			this.content.attr( "tabindex", "0" );

			this.content.attr( "role", "menu" );

			this.content.addClass( this.options.contentClass );
			this.content.id = id;
		},

		_bindEvents: function(){
			var self = this;

			this.header
				// use the tappy plugin if it's available
				// tap can't be namespaced yet without special events api: https://github.com/filamentgroup/tappy/issues/22
				.bind( ( window.tappy ? "tap" : "click" ), function( e ){
					self.toggle( e.target );
					e.preventDefault();
				});

			this.element
				.bind( "keydown." + pluginName, function( e ){
					if( ( e.which === 13 || e.which === 32 ) ){
						self.toggle( e.target );
						e.preventDefault();
					}
				});

			if( this.options.collapsed ){
				this.collapse( false );
			}
			else {
				this.expand();
			}
		},

		collapsed: false,

		expand: function ( target ) {
			var self = $( this ).data( pluginName ) || this;
			self.element.removeClass( self.options.collapsedClass );
			self.collapsed = false;
			self.header.attr( "aria-expanded", "true" );
			self.content.attr( "aria-hidden", "false" );
			self.element.trigger( "expand" );
		},

		collapse: function( target ) {
			var self = $( this ).data( pluginName ) || this;
			self.element.addClass( self.options.collapsedClass );
			self.collapsed = true;
			self.header.attr( "aria-expanded", "false" );
			self.content.attr( "aria-hidden", "true" );
			self.element.trigger( "collapse" );
		},

		toggle: function( target ){
			if(  this.collapsed ){
				this.expand( target );
			} else {
				this.collapse( target );
			}
		}

	};

	// lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[ pluginName ] = function (options) {
		return this.each(function () {
			if ( !$( this ).data( pluginName ) ) {
				$( this ).data( pluginName, new Plugin( this, options ));
			}
		});
	};

	// Simple auto-init by selector that runs when the dom is ready. Trigger "enhance" if desirable.
	$( document ).bind( "enhance", function( e ){
		var selector = "." + pluginName;
		$( $( e.target ).is( selector ) && e.target ).add( selector, e.target ).filter( selector )[ pluginName ]();
	});

})(jQuery, window, document);
<|endoftext|>


<commit_before>/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Definitions for W3C's Navigation Timing specification.
 *
 * Created from
 * http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
 *
 * @externs
 */

/** @constructor */
function PerformanceTiming() {}
/** @type {number} */ PerformanceTiming.prototype.navigationStart;
/** @type {number} */ PerformanceTiming.prototype.unloadEventStart;
/** @type {number} */ PerformanceTiming.prototype.unloadEventEnd;
/** @type {number} */ PerformanceTiming.prototype.redirectStart;
/** @type {number} */ PerformanceTiming.prototype.redirectEnd;
/** @type {number} */ PerformanceTiming.prototype.fetchStart;
/** @type {number} */ PerformanceTiming.prototype.domainLookupStart;
/** @type {number} */ PerformanceTiming.prototype.domainLookupEnd;
/** @type {number} */ PerformanceTiming.prototype.connectStart;
/** @type {number} */ PerformanceTiming.prototype.connectEnd;
/** @type {number} */ PerformanceTiming.prototype.secureConnectionStart;
/** @type {number} */ PerformanceTiming.prototype.requestStart;
/** @type {number} */ PerformanceTiming.prototype.responseStart;
/** @type {number} */ PerformanceTiming.prototype.responseEnd;
/** @type {number} */ PerformanceTiming.prototype.domLoading;
/** @type {number} */ PerformanceTiming.prototype.domInteractive;
/** @type {number} */ PerformanceTiming.prototype.domContentLoadedEventStart;
/** @type {number} */ PerformanceTiming.prototype.domContentLoadedEventEnd;
/** @type {number} */ PerformanceTiming.prototype.domComplete;
/** @type {number} */ PerformanceTiming.prototype.loadEventStart;
/** @type {number} */ PerformanceTiming.prototype.loadEventEnd;

/** @constructor */
function PerformanceNavigation() {}
/** @type {number} */ PerformanceNavigation.prototype.TYPE_NAVIGATE = 0;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_RELOAD = 1;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_BACK_FORWARD = 2;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_RESERVED = 255;
/** @type {number} */ PerformanceNavigation.prototype.type;
/** @type {number} */ PerformanceNavigation.prototype.redirectCount;

// Only available in WebKit, and only with the --enable-memory-info flag.
/** @constructor */
function PerformanceMemory() {}
/** @type {number} */ PerformanceMemory.prototype.jsHeapSizeLimit;
/** @type {number} */ PerformanceMemory.prototype.totalJSHeapSize;
/** @type {number} */ PerformanceMemory.prototype.usedJSHeapSize;

/** @constructor */
function Performance() {}
/** @type {PerformanceTiming} */ Performance.prototype.timing;
/** @type {PerformanceNavigation} */ Performance.prototype.navigation;

// Only available in WebKit, and only with the --enable-memory-info flag.
/** @type {PerformanceMemory} */ Performance.prototype.memory;

/** @type {Performance} */
Window.prototype.performance;
<commit_msg>Add missing definitions to the w3c_navigation_timings extern. Contributed by leeight [gmail.com]<commit_after>/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Definitions for W3C's Navigation Timing specification.
 *
 * Created from
 * @see http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
 * @see http://w3c-test.org/webperf/specs/ResourceTiming
 * @see http://www.w3.org/TR/performance-timeline
 *
 * @externs
 */

/** @constructor */
function PerformanceTiming() {}
/** @type {number} */ PerformanceTiming.prototype.navigationStart;
/** @type {number} */ PerformanceTiming.prototype.unloadEventStart;
/** @type {number} */ PerformanceTiming.prototype.unloadEventEnd;
/** @type {number} */ PerformanceTiming.prototype.redirectStart;
/** @type {number} */ PerformanceTiming.prototype.redirectEnd;
/** @type {number} */ PerformanceTiming.prototype.fetchStart;
/** @type {number} */ PerformanceTiming.prototype.domainLookupStart;
/** @type {number} */ PerformanceTiming.prototype.domainLookupEnd;
/** @type {number} */ PerformanceTiming.prototype.connectStart;
/** @type {number} */ PerformanceTiming.prototype.connectEnd;
/** @type {number} */ PerformanceTiming.prototype.secureConnectionStart;
/** @type {number} */ PerformanceTiming.prototype.requestStart;
/** @type {number} */ PerformanceTiming.prototype.responseStart;
/** @type {number} */ PerformanceTiming.prototype.responseEnd;
/** @type {number} */ PerformanceTiming.prototype.domLoading;
/** @type {number} */ PerformanceTiming.prototype.domInteractive;
/** @type {number} */ PerformanceTiming.prototype.domContentLoadedEventStart;
/** @type {number} */ PerformanceTiming.prototype.domContentLoadedEventEnd;
/** @type {number} */ PerformanceTiming.prototype.domComplete;
/** @type {number} */ PerformanceTiming.prototype.loadEventStart;
/** @type {number} */ PerformanceTiming.prototype.loadEventEnd;

/** @constructor */
function PerformanceEntry() {}
/** @type {string} */ PerformanceEntry.prototype.name;
/** @type {string} */ PerformanceEntry.prototype.entryType;
/** @type {number} */ PerformanceEntry.prototype.startTime;
/** @type {number} */ PerformanceEntry.prototype.duration;

/**
 * @constructor
 * @extends {PerformanceEntry}
 */
function PerformanceResourceTiming() {}
/** @type {number} */ PerformanceResourceTiming.prototype.redirectStart;
/** @type {number} */ PerformanceResourceTiming.prototype.redirectEnd;
/** @type {number} */ PerformanceResourceTiming.prototype.fetchStart;
/** @type {number} */ PerformanceResourceTiming.prototype.domainLookupStart;
/** @type {number} */ PerformanceResourceTiming.prototype.domainLookupEnd;
/** @type {number} */ PerformanceResourceTiming.prototype.connectStart;
/** @type {number} */ PerformanceResourceTiming.prototype.connectEnd;
/** @type {number} */
PerformanceResourceTiming.prototype.secureConnectionStart;
/** @type {number} */ PerformanceResourceTiming.prototype.requestStart;
/** @type {number} */ PerformanceResourceTiming.prototype.responseStart;
/** @type {number} */ PerformanceResourceTiming.prototype.responseEnd;
/** @type {string} */ PerformanceResourceTiming.prototype.initiatorType;

/** @constructor */
function PerformanceNavigation() {}
/** @type {number} */ PerformanceNavigation.prototype.TYPE_NAVIGATE = 0;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_RELOAD = 1;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_BACK_FORWARD = 2;
/** @type {number} */ PerformanceNavigation.prototype.TYPE_RESERVED = 255;
/** @type {number} */ PerformanceNavigation.prototype.type;
/** @type {number} */ PerformanceNavigation.prototype.redirectCount;

// Only available in WebKit, and only with the --enable-memory-info flag.
/** @constructor */
function PerformanceMemory() {}
/** @type {number} */ PerformanceMemory.prototype.jsHeapSizeLimit;
/** @type {number} */ PerformanceMemory.prototype.totalJSHeapSize;
/** @type {number} */ PerformanceMemory.prototype.usedJSHeapSize;

/** @constructor */
function Performance() {}
/** @type {PerformanceTiming} */ Performance.prototype.timing;
/** @type {PerformanceNavigation} */ Performance.prototype.navigation;

/**
 * Clears the buffer used to store the current list of
 * PerformanceResourceTiming resources.
 * @return {undefined}
 */
Performance.prototype.clearResourceTimings = function() {};

/**
 * Set the maximum number of PerformanceResourceTiming resources that may be
 * stored in the buffer.
 * @param {number} maxSize
 */
Performance.prototype.setResourceTimingBufferSize = function(maxSize) {};

/**
 * @return {Array.<PerformanceEntry>} PerformanceEntry list in chronological
 *     order with respect to startTime.
 */
Performance.prototype.getEntries = function() {};

/**
 * @param {string} entryType The entryType used to filter the list.
 * @return {Array.<PerformanceEntry>} PerformanceEntry list in chronological
 *     order with respect to startTime.
 */
Performance.prototype.getEntriesByType = function(entryType) {};

/**
 * @param {string} name The same value for the name attribute of
 *     PerformanceEntry.
 * @param {string=} opt_entryType The same value for the entryType attribute of
 *     PerformanceEntry.
 * @return {Array.<PerformanceEntry>} PerformanceEntry list in chronological
 *     order with respect to startTime.
 */
Performance.prototype.getEntriesByName = function(name, opt_entryType) {};

// Only available in WebKit, and only with the --enable-memory-info flag.
/** @type {PerformanceMemory} */ Performance.prototype.memory;

/** @type {Performance} */
Window.prototype.performance;
<|endoftext|>


<commit_before>var demand = require('must');
var CodeType = require('../CodeType');

exports.initList = function (List) {
	List.add({
		code: { type: CodeType },
		nested: {
			code: { type: CodeType },
		},
		lang: {
			type: CodeType,
			lang: 'c',
		},
		language: {
			type: CodeType,
			lang: 'js',
		},
		codemirror: {
			type: CodeType,
			lang: 'html',
			codemirror: {
				value: 'fooga',
			},
		},
	});
};

exports.createData = function (List) { // eslint-disable-line no-unused-vars

};

exports.testFilters = function (List) { // eslint-disable-line no-unused-vars

};

exports.testFieldType = function (List) {
	var testItem = new List.model();

	it('should update top level fields', function (done) {
		List.fields.code.updateItem(testItem, {
			code: 'foo(bar);',
		}, function () {
			demand(testItem.code).be('foo(bar);');
			testItem.code = undefined;
			done();
		});
	});

	it('should update nested fields', function (done) {
		List.fields['nested.code'].updateItem(testItem, {
			nested: {
				code: 'foo(bar);',
			},
		}, function () {
			demand(testItem.nested.code).be('foo(bar);');
			testItem.nested.code = undefined;
			done();
		});
	});

	it('should update nested fields with flat paths', function (done) {
		List.fields['nested.code'].updateItem(testItem, {
			'nested.code': 'foo(bar);',
		}, function () {
			demand(testItem.nested.code).be('foo(bar);');
			testItem.nested.code = undefined;
			done();
		});
	});

	it('should handle a `lang` config property', function () {
		demand(List.fields.lang.lang).be('c');
	});

	it('should handle a `language` config property', function () {
		demand(List.fields.language.lang).be('js');
	});

	it('should support a `codemirror` config property', function () {
		demand(List.fields.codemirror.codemirror).be.object();
		demand(List.fields.codemirror.codemirror.value).be('fooga');
	});

	it('should merge the `lang` and `codemirror` config properties', function () {
		demand(List.fields.codemirror.editor).be.object();
		demand(List.fields.codemirror.editor.mode).be('html');
		demand(List.fields.codemirror.editor.value).be('fooga');
	});
};
<commit_msg>Removing unused code from Code field tests<commit_after>var demand = require('must');
var CodeType = require('../CodeType');

exports.initList = function (List) {
	List.add({
		code: { type: CodeType },
		nested: {
			code: { type: CodeType },
		},
		lang: {
			type: CodeType,
			lang: 'c',
		},
		language: {
			type: CodeType,
			lang: 'js',
		},
		codemirror: {
			type: CodeType,
			lang: 'html',
			codemirror: {
				value: 'codemirror value',
			},
		},
	});
};

exports.testFieldType = function (List) {
	var testItem = new List.model();

	it('should update top level fields', function (done) {
		List.fields.code.updateItem(testItem, {
			code: 'foo(bar);',
		}, function () {
			demand(testItem.code).be('foo(bar);');
			testItem.code = undefined;
			done();
		});
	});

	it('should update nested fields', function (done) {
		List.fields['nested.code'].updateItem(testItem, {
			nested: {
				code: 'foo(bar);',
			},
		}, function () {
			demand(testItem.nested.code).be('foo(bar);');
			testItem.nested.code = undefined;
			done();
		});
	});

	it('should update nested fields with flat paths', function (done) {
		List.fields['nested.code'].updateItem(testItem, {
			'nested.code': 'foo(bar);',
		}, function () {
			demand(testItem.nested.code).be('foo(bar);');
			testItem.nested.code = undefined;
			done();
		});
	});

	it('should handle a `lang` config property', function () {
		demand(List.fields.lang.lang).be('c');
	});

	it('should handle a `language` config property', function () {
		demand(List.fields.language.lang).be('js');
	});

	it('should support a `codemirror` config property', function () {
		demand(List.fields.codemirror.codemirror).be.object();
		demand(List.fields.codemirror.codemirror.value).be('codemirror value');
	});

	it('should merge the `lang` and `codemirror` config properties', function () {
		demand(List.fields.codemirror.editor).be.object();
		demand(List.fields.codemirror.editor.mode).be('html');
		demand(List.fields.codemirror.editor.value).be('codemirror value');
	});
};
<|endoftext|>