<commit_before>/**
 * Module dependencies
 */
var request = require('superagent')
  , xml2js = require('xml2js')
  , debug = require('debug')('linkshare-lookup')
  , _ = require('underscore');

// API endpoint
var endpoint = 'http://productsearch.linksynergy.com/productsearch';

var Linkshare = module.exports = function (opts) {
  if (!(this instanceof Linkshare)) return new Linkshare (opts);

  this._keywords = opts.keywords;
};

Linkshare.prototype.id = function (id) {
  debug('set id %s', id);
  return this._id = id, this;
};

Linkshare.prototype.advertiser = function (id) {
  debug('set advertiser id %s', id);
  return this._advertiser = id, this;
};

Linkshare.prototype.limit = function (limit) {
  debug('set limit %d', limit);
  if (!limit) return this;
  return this._limit = limit, this;
};

Linkshare.prototype.one = function (one) {
  debug('set one %d', one);
  one = ('undefined' === typeof one) ? true : !!one;
  return this._one = one, this;
};

Linkshare.prototype.page = function (page) {
  debug('set page %d', page);
  return this._page = page, this;
};

Linkshare.prototype.done = function (cb) {
  var one = this._one
    , limit = this._limit;

  debug('running request');

  return request
    .get(endpoint)
    .query({token: this._id})
    .query({keyword: this._keywords})
    .query({mid: this._advertiser})
    .query({max: one ? 1 : limit})
    .query({pagenumber: this._page})
    .end(function (err, res) {
      // Catch http errors
      if (err) return cb(err);

      xml2js.parseString(res.text, {trim: true}, function (err, res) {
        // Catch parsing errors
        if (err) return cb(err);

        var errors = res.result.Errors
          , items = res.result.item;

        // Catch api errors
        if (errors) return cb(new Error(formatError(errors)));

        // Format results
        var formatted = format(items);

        // Limit results
        if (one) {
          formatted = _.first(formatted) || null;
        } else if (limit) {
          formatted = _.first(formatted, limit);
        }

        return cb(null, formatted);
      });
    });
};

var format = function (items) {
  return items.map(function (i) {
    return {
      name: i.productname[0],
      listPrice: i.price[0]['_'],
      currency: i.price[0]['$']['currency'],
      url: i.linkurl[0],
      id: i.linkid[0]
    }
  });
};

var formatError = function (error) {
  return error[0].ErrorText;
};
<commit_msg>Defend against no results<commit_after>/**
 * Module dependencies
 */
var request = require('superagent')
  , xml2js = require('xml2js')
  , debug = require('debug')('linkshare-lookup')
  , _ = require('underscore');

// API endpoint
var endpoint = 'http://productsearch.linksynergy.com/productsearch';

var Linkshare = module.exports = function (opts) {
  if (!(this instanceof Linkshare)) return new Linkshare (opts);

  this._keywords = opts.keywords;
};

Linkshare.prototype.id = function (id) {
  debug('set id %s', id);
  return this._id = id, this;
};

Linkshare.prototype.advertiser = function (id) {
  debug('set advertiser id %s', id);
  return this._advertiser = id, this;
};

Linkshare.prototype.limit = function (limit) {
  debug('set limit %d', limit);
  if (!limit) return this;
  return this._limit = limit, this;
};

Linkshare.prototype.one = function (one) {
  debug('set one %d', one);
  one = ('undefined' === typeof one) ? true : !!one;
  return this._one = one, this;
};

Linkshare.prototype.page = function (page) {
  debug('set page %d', page);
  return this._page = page, this;
};

Linkshare.prototype.done = function (cb) {
  var one = this._one
    , limit = this._limit;

  debug('running request');

  return request
    .get(endpoint)
    .query({token: this._id})
    .query({keyword: this._keywords})
    .query({mid: this._advertiser})
    .query({max: one ? 1 : limit})
    .query({pagenumber: this._page})
    .end(function (err, res) {
      // Catch http errors
      if (err) return cb(err);

      xml2js.parseString(res.text, {trim: true}, function (err, res) {
        // Catch parsing errors
        if (err) return cb(err);

        var errors = res.result.Errors
          , items = res.result.item || [];

        // Catch api errors
        if (errors) return cb(new Error(formatError(errors)));

        // Format results
        var formatted = format(items);

        // Limit results
        if (one) {
          formatted = _.first(formatted) || null;
        } else if (limit) {
          formatted = _.first(formatted, limit);
        }

        return cb(null, formatted);
      });
    });
};

var format = function (items) {
  return items.map(function (i) {
    return {
      name: i.productname[0],
      listPrice: i.price[0]['_'],
      currency: i.price[0]['$']['currency'],
      url: i.linkurl[0],
      id: i.linkid[0]
    }
  });
};

var formatError = function (error) {
  return error[0].ErrorText;
};
<|endoftext|>


<commit_before>var loaderUtils = require('loader-utils');
var webfontsGenerator = require('webfonts-generator');
var path = require('path');
var glob = require('glob');
var url = require('url');
var hashFiles = require('./utils').hashFiles;

var mimeTypes = {
  'eot': 'application/vnd.ms-fontobject',
  'svg': 'image/svg+xml',
  'ttf': 'application/x-font-ttf',
  'woff': 'application/font-woff',
  'woff2': 'font/woff2'
};

function getFilesAndDeps (patterns, context) {
  var files = [];
  var filesDeps = [];
  var directoryDeps = [];

  function addFile (file) {
    filesDeps.push(file);
    files.push(path.resolve(context, file));
  }

  function addByGlob (globExp) {
    var globOptions = {
      cwd: context
    };

    var foundFiles = glob.sync(globExp, globOptions);
    files = files.concat(foundFiles.map(file => {
      return path.resolve(context, file);
    }));

    var globDirs = glob.sync(path.dirname(globExp) + '/', globOptions);
    directoryDeps = directoryDeps.concat(globDirs.map(file => {
      return path.resolve(context, file);
    }));
  }

  // Re-work the files array.
  patterns.forEach(function (pattern) {
    if (glob.hasMagic(pattern)) {
      addByGlob(pattern);
    } else {
      addFile(pattern);
    }
  });

  return {
    files: files,
    dependencies: {
      directories: directoryDeps,
      files: filesDeps
    }
  };
}

// Futureproof webpack option parsing
function wpGetOptions (context) {
  if (typeof context.query === 'string') return loaderUtils.getOptions(context);
  return context.query;
}

module.exports = function (content) {
  this.cacheable();

  var webpackOptions = this.options || {}; // only makes sense in Webpack 1.x, or when LoaderOptionsPlugin is used
  var options = wpGetOptions(this) || {};
  var rawFontConfig;
  try {
    rawFontConfig = JSON.parse(content);
  } catch (ex) {
    rawFontConfig = this.exec(content, this.resourcePath);
  }
  var fontConfig = Object.assign({}, options, rawFontConfig);

  var filesAndDeps = getFilesAndDeps(fontConfig.files, this.context);
  filesAndDeps.dependencies.files.forEach(this.addDependency.bind(this));
  filesAndDeps.dependencies.directories.forEach(this.addContextDependency.bind(this));
  fontConfig.files = filesAndDeps.files;

  // With everything set up, let's make an ACTUAL config.
  var formats = fontConfig.types || ['eot', 'woff', 'woff2', 'ttf', 'svg'];
  if (formats.constructor !== Array) {
    formats = [formats];
  }

  var generatorOptions = {
    files: fontConfig.files,
    fontName: fontConfig.fontName,
    types: formats,
    order: formats,
    fontHeight: fontConfig.fontHeight || 1000, // Fixes conversion issues with small svgs,
    codepoints: fontConfig.codepoints || {},
    templateOptions: {
      baseSelector: fontConfig.baseSelector || '.icon',
      classPrefix: 'classPrefix' in fontConfig ? fontConfig.classPrefix : 'icon-'
    },
    scssFile: fontConfig.scssFile || false,
    dest: fontConfig.dest || '',
    html: fontConfig.html || false,
    htmlDest: fontConfig.htmlDest || undefined,
    writeFiles: fontConfig.writeFiles || false,
    cssFontsUrl: fontConfig.cssFontsUrl || '',
    embed: fontConfig.embed || false,
    formatOptions: fontConfig.formatOptions || {}
  };

  // Add key only if it exists in config object to avoid fs errors
  if ('htmlTemplate' in fontConfig) {
    generatorOptions.htmlTemplate = fontConfig.htmlTemplate;
  }

  // This originally was in the object notation itself.
  // Unfortunately that actually broke my editor's syntax-highlighting...
  // ... what a shame.
  if (typeof fontConfig.rename === 'function') {
    generatorOptions.rename = fontConfig.rename;
  } else {
    generatorOptions.rename = function (f) {
      return path.basename(f, '.svg');
    };
  }

  if (fontConfig.cssTemplate) {
    generatorOptions.cssTemplate = path.resolve(this.context, fontConfig.cssTemplate);
  }

  if (fontConfig.cssFontsPath) {
    generatorOptions.cssFontsPath = path.resolve(this.context, fontConfig.cssFontsPath);
  }

  if (fontConfig.htmlTemplate) {
    generatorOptions.htmlTemplate = path.resolve(this.context, fontConfig.htmlTemplate);
  }

  if (fontConfig.htmlDest) {
    generatorOptions.htmlDest = path.resolve(this.context, fontConfig.htmlDest);
  }

  if (fontConfig.dest) {
    generatorOptions.dest = path.resolve(this.context, fontConfig.dest);
  }

  // Spit out SCSS file to same path as CSS file to easily use mixins (scssFile must be true)
  if (fontConfig.scssFile === true) {
    generatorOptions.cssDest = path.resolve(this.context, fontConfig.dest, fontConfig.fontName + '.scss')
  }

  // svgicons2svgfont stuff
  var keys = [
    'fixedWidth',
    'centerHorizontally',
    'normalize',
    'fontHeight',
    'round',
    'descent'
  ];
  for (var x in keys) {
    if (typeof fontConfig[keys[x]] !== 'undefined') {
      generatorOptions[keys[x]] = fontConfig[keys[x]];
    }
  }

  var cb = this.async();

  var publicPath = options.publicPath || (webpackOptions.output && webpackOptions.output.publicPath) || '/';
  var embed = !!generatorOptions.embed;

  if (generatorOptions.cssTemplate) {
    this.addDependency(generatorOptions.cssTemplate);
  }

  if (generatorOptions.cssFontsPath) {
    this.addDependency(generatorOptions.cssFontsPath);
  }

  webfontsGenerator(generatorOptions, (err, res) => {
    if (err) {
      return cb(err);
    }
    var urls = {};
    for (var i in formats) {
      var format = formats[i];
      var filename = fontConfig.fileName || options.fileName || '[chunkhash]-[fontname].[ext]';
      var chunkHash = filename.indexOf('[chunkhash]') !== -1
        ? hashFiles(generatorOptions.files, options.hashLength) : '';

      filename = filename
        .replace('[chunkhash]', chunkHash)
        .replace('[fontname]', generatorOptions.fontName)
        .replace('[ext]', format);

      if (!embed) {
        var formatFilename = loaderUtils.interpolateName(this,
          filename,
          {
            context: this.rootContext || this.options.context || this.context,
            content: res[format]
          }
        );
        urls[format] = url.resolve(publicPath, formatFilename.replace(/\\/g, '/'));
        this.emitFile(formatFilename, res[format]);
      } else {
        urls[format] = 'data:' +
        mimeTypes[format] +
        ';charset=utf-8;base64,' +
        (Buffer.from(res[format]).toString('base64'));
      }
    }
    var emitCodepointsOptions = fontConfig.emitCodepoints || options.emitCodepoints || null;
    if (emitCodepointsOptions) {
      const emitCodepoints = require('./emit-codepoints');
      emitCodepoints.emitFiles(this, emitCodepointsOptions, generatorOptions, options);
    }

    cb(null, res.generateCss(urls));
  });
};<commit_msg>Added extra line at end of document.<commit_after>var loaderUtils = require('loader-utils');
var webfontsGenerator = require('webfonts-generator');
var path = require('path');
var glob = require('glob');
var url = require('url');
var hashFiles = require('./utils').hashFiles;

var mimeTypes = {
  'eot': 'application/vnd.ms-fontobject',
  'svg': 'image/svg+xml',
  'ttf': 'application/x-font-ttf',
  'woff': 'application/font-woff',
  'woff2': 'font/woff2'
};

function getFilesAndDeps (patterns, context) {
  var files = [];
  var filesDeps = [];
  var directoryDeps = [];

  function addFile (file) {
    filesDeps.push(file);
    files.push(path.resolve(context, file));
  }

  function addByGlob (globExp) {
    var globOptions = {
      cwd: context
    };

    var foundFiles = glob.sync(globExp, globOptions);
    files = files.concat(foundFiles.map(file => {
      return path.resolve(context, file);
    }));

    var globDirs = glob.sync(path.dirname(globExp) + '/', globOptions);
    directoryDeps = directoryDeps.concat(globDirs.map(file => {
      return path.resolve(context, file);
    }));
  }

  // Re-work the files array.
  patterns.forEach(function (pattern) {
    if (glob.hasMagic(pattern)) {
      addByGlob(pattern);
    } else {
      addFile(pattern);
    }
  });

  return {
    files: files,
    dependencies: {
      directories: directoryDeps,
      files: filesDeps
    }
  };
}

// Futureproof webpack option parsing
function wpGetOptions (context) {
  if (typeof context.query === 'string') return loaderUtils.getOptions(context);
  return context.query;
}

module.exports = function (content) {
  this.cacheable();

  var webpackOptions = this.options || {}; // only makes sense in Webpack 1.x, or when LoaderOptionsPlugin is used
  var options = wpGetOptions(this) || {};
  var rawFontConfig;
  try {
    rawFontConfig = JSON.parse(content);
  } catch (ex) {
    rawFontConfig = this.exec(content, this.resourcePath);
  }
  var fontConfig = Object.assign({}, options, rawFontConfig);

  var filesAndDeps = getFilesAndDeps(fontConfig.files, this.context);
  filesAndDeps.dependencies.files.forEach(this.addDependency.bind(this));
  filesAndDeps.dependencies.directories.forEach(this.addContextDependency.bind(this));
  fontConfig.files = filesAndDeps.files;

  // With everything set up, let's make an ACTUAL config.
  var formats = fontConfig.types || ['eot', 'woff', 'woff2', 'ttf', 'svg'];
  if (formats.constructor !== Array) {
    formats = [formats];
  }

  var generatorOptions = {
    files: fontConfig.files,
    fontName: fontConfig.fontName,
    types: formats,
    order: formats,
    fontHeight: fontConfig.fontHeight || 1000, // Fixes conversion issues with small svgs,
    codepoints: fontConfig.codepoints || {},
    templateOptions: {
      baseSelector: fontConfig.baseSelector || '.icon',
      classPrefix: 'classPrefix' in fontConfig ? fontConfig.classPrefix : 'icon-'
    },
    scssFile: fontConfig.scssFile || false,
    dest: fontConfig.dest || '',
    html: fontConfig.html || false,
    htmlDest: fontConfig.htmlDest || undefined,
    writeFiles: fontConfig.writeFiles || false,
    cssFontsUrl: fontConfig.cssFontsUrl || '',
    embed: fontConfig.embed || false,
    formatOptions: fontConfig.formatOptions || {}
  };

  // Add key only if it exists in config object to avoid fs errors
  if ('htmlTemplate' in fontConfig) {
    generatorOptions.htmlTemplate = fontConfig.htmlTemplate;
  }

  // This originally was in the object notation itself.
  // Unfortunately that actually broke my editor's syntax-highlighting...
  // ... what a shame.
  if (typeof fontConfig.rename === 'function') {
    generatorOptions.rename = fontConfig.rename;
  } else {
    generatorOptions.rename = function (f) {
      return path.basename(f, '.svg');
    };
  }

  if (fontConfig.cssTemplate) {
    generatorOptions.cssTemplate = path.resolve(this.context, fontConfig.cssTemplate);
  }

  if (fontConfig.cssFontsPath) {
    generatorOptions.cssFontsPath = path.resolve(this.context, fontConfig.cssFontsPath);
  }

  if (fontConfig.htmlTemplate) {
    generatorOptions.htmlTemplate = path.resolve(this.context, fontConfig.htmlTemplate);
  }

  if (fontConfig.htmlDest) {
    generatorOptions.htmlDest = path.resolve(this.context, fontConfig.htmlDest);
  }

  if (fontConfig.dest) {
    generatorOptions.dest = path.resolve(this.context, fontConfig.dest);
  }

  // Spit out SCSS file to same path as CSS file to easily use mixins (scssFile must be true)
  if (fontConfig.scssFile === true) {
    generatorOptions.cssDest = path.resolve(this.context, fontConfig.dest, fontConfig.fontName + '.scss')
  }

  // svgicons2svgfont stuff
  var keys = [
    'fixedWidth',
    'centerHorizontally',
    'normalize',
    'fontHeight',
    'round',
    'descent'
  ];
  for (var x in keys) {
    if (typeof fontConfig[keys[x]] !== 'undefined') {
      generatorOptions[keys[x]] = fontConfig[keys[x]];
    }
  }

  var cb = this.async();

  var publicPath = options.publicPath || (webpackOptions.output && webpackOptions.output.publicPath) || '/';
  var embed = !!generatorOptions.embed;

  if (generatorOptions.cssTemplate) {
    this.addDependency(generatorOptions.cssTemplate);
  }

  if (generatorOptions.cssFontsPath) {
    this.addDependency(generatorOptions.cssFontsPath);
  }

  webfontsGenerator(generatorOptions, (err, res) => {
    if (err) {
      return cb(err);
    }
    var urls = {};
    for (var i in formats) {
      var format = formats[i];
      var filename = fontConfig.fileName || options.fileName || '[chunkhash]-[fontname].[ext]';
      var chunkHash = filename.indexOf('[chunkhash]') !== -1
        ? hashFiles(generatorOptions.files, options.hashLength) : '';

      filename = filename
        .replace('[chunkhash]', chunkHash)
        .replace('[fontname]', generatorOptions.fontName)
        .replace('[ext]', format);

      if (!embed) {
        var formatFilename = loaderUtils.interpolateName(this,
          filename,
          {
            context: this.rootContext || this.options.context || this.context,
            content: res[format]
          }
        );
        urls[format] = url.resolve(publicPath, formatFilename.replace(/\\/g, '/'));
        this.emitFile(formatFilename, res[format]);
      } else {
        urls[format] = 'data:' +
        mimeTypes[format] +
        ';charset=utf-8;base64,' +
        (Buffer.from(res[format]).toString('base64'));
      }
    }
    var emitCodepointsOptions = fontConfig.emitCodepoints || options.emitCodepoints || null;
    if (emitCodepointsOptions) {
      const emitCodepoints = require('./emit-codepoints');
      emitCodepoints.emitFiles(this, emitCodepointsOptions, generatorOptions, options);
    }

    cb(null, res.generateCss(urls));
  });
};
<|endoftext|>


<commit_before>var $ = require('dom')

module.exports = Button

function Button(ele, opts) {
  if (!(this instanceof Button))
    return new Button(ele, opts)

  this.opts = {
    loadingText: opts.loadingText || 'loading...'
  }
  this.ele = $(ele)
  this.isLoading = false
}

Button.prototype.setState = function(state) {
  var d = 'disabled'
  var val = this.isInput(this.ele) ? 'val' : 'html'
  state = state + 'Text'
  var state_ = state + '-text'
  if (this.ele.data('reset-text') == null)
    this.ele.data('reset-text', this.ele[val]())

  if (this.ele.data(state_) == null) {
    this.ele[val](this.options[state_])
  } else {
    this.ele[val](this.ele.data(state_))
  }
}

Button.prototype.isInput = function(el) {
  if (!el) return false
  var tag = el.tagName && el.tagName.toLowerCase()
  return tag && tag === 'input'
}
<commit_msg>Allow either a string or object for opts<commit_after>var $ = require('dom')

module.exports = Button

function Button(ele, opts) {
  if (!(this instanceof Button))
    return new Button(ele, opts)

  if ('string' === typeof opts) {
    opts = {
      loadingText: opts
    }
  }
  opts = opts || {}
  this.opts = {
    loadingText: opts.loadingText || 'loading...'
  }
  this.ele = $(ele)
  this.isLoading = false
}

Button.prototype.setState = function(state) {
  var d = 'disabled'
  var val = this.isInput(this.ele) ? 'val' : 'html'
  state = state + 'Text'
  var state_ = state + '-text'
  if (this.ele.data('reset-text') == null)
    this.ele.data('reset-text', this.ele[val]())

  if (this.ele.data(state_) == null) {
    this.ele[val](this.options[state_])
  } else {
    this.ele[val](this.ele.data(state_))
  }
}

Button.prototype.isInput = function(el) {
  if (!el) return false
  var tag = el.tagName && el.tagName.toLowerCase()
  return tag && tag === 'input'
}
<|endoftext|>


<commit_before>'use strict';
var fs = require('fs');
var path = require('path');
var childProcess = require('child_process');
var argv = require('the-argv');
var readPkgUp = require('read-pkg-up');
var writePkg = require('write-pkg');
var Promise = require('pinkie-promise');
var pify = require('pify');
var arrExclude = require('arr-exclude');
var DEFAULT_TEST_SCRIPT = 'echo "Error: no test specified" && exit 1';

module.exports = function (opts) {
	opts = opts || {};

	var ret = readPkgUp.sync({
		cwd: opts.cwd,
		normalize: false
	});
	var pkg = ret.pkg;
	var pkgPath = ret.path;
	var cli = opts.args || argv();
	var args = arrExclude(cli, ['--init', '--unicorn']);
	var cmd = 'ava' + (args.length > 0 ? ' ' + args.join(' ') : '');
	var s = pkg.scripts = pkg.scripts ? pkg.scripts : {};

	if (s.test && s.test !== DEFAULT_TEST_SCRIPT) {
		s.test = s.test.replace(/\bnode (test\/)?test\.js\b/, cmd);

		if (!/\bava\b/.test(s.test)) {
			s.test += ' && ' + cmd;
		}
	} else {
		s.test = cmd;
	}

	writePkg.sync(pkgPath, pkg);

	return pify(childProcess.execFile, Promise)('npm', ['install', '--save-dev', 'ava'], {
		cwd: path.dirname(pkgPath)
	}).then(function () {
		// for personal use
		if (cli.indexOf('--unicorn') !== -1) {
			var pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
			pkg.devDependencies.ava = '*';
			writePkg.sync(pkgPath, pkg);
		}
	});
};
<commit_msg>fallback to defaults if package.json can't be found<commit_after>'use strict';
var fs = require('fs');
var path = require('path');
var childProcess = require('child_process');
var argv = require('the-argv');
var readPkgUp = require('read-pkg-up');
var writePkg = require('write-pkg');
var Promise = require('pinkie-promise');
var pify = require('pify');
var arrExclude = require('arr-exclude');
var DEFAULT_TEST_SCRIPT = 'echo "Error: no test specified" && exit 1';

module.exports = function (opts) {
	opts = opts || {};

	var ret = readPkgUp.sync({
		cwd: opts.cwd,
		normalize: false
	});
	var pkg = ret.pkg || {};
	var pkgPath = ret.path || path.resolve(opts.cwd || '', 'package.json');
	var cli = opts.args || argv();
	var args = arrExclude(cli, ['--init', '--unicorn']);
	var cmd = 'ava' + (args.length > 0 ? ' ' + args.join(' ') : '');
	var s = pkg.scripts = pkg.scripts ? pkg.scripts : {};

	if (s.test && s.test !== DEFAULT_TEST_SCRIPT) {
		s.test = s.test.replace(/\bnode (test\/)?test\.js\b/, cmd);

		if (!/\bava\b/.test(s.test)) {
			s.test += ' && ' + cmd;
		}
	} else {
		s.test = cmd;
	}

	writePkg.sync(pkgPath, pkg);

	return pify(childProcess.execFile, Promise)('npm', ['install', '--save-dev', 'ava'], {
		cwd: path.dirname(pkgPath)
	}).then(function () {
		// for personal use
		if (cli.indexOf('--unicorn') !== -1) {
			var pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
			pkg.devDependencies.ava = '*';
			writePkg.sync(pkgPath, pkg);
		}
	});
};
<|endoftext|>


<commit_before>var Promise = require("bluebird");
var AlexaUtterances = require("alexa-utterances");
var SSML = require("./to-ssml");
var alexa = {};

alexa.response = function() {
  this.resolved = false;
  this.response = {
    "version": "1.0",
    "sessionAttributes": {},
    "response": {
      "shouldEndSession": true
    }
  };
  this.say = function(str) {
    if (typeof this.response.response.outputSpeech == "undefined") {
      this.response.response.outputSpeech = {
        "type": "SSML",
        "ssml": SSML.fromStr(str)
      };
    } else {
      // append str to the current outputSpeech, stripping the out speak tag
      this.response.response.outputSpeech.ssml = SSML.fromStr(str, this.response.response.outputSpeech.ssml);
    }
    return this;
  };
  this.clear = function(/*str*/) {
    this.response.response.outputSpeech = {
      "type": "PlainText",
      "text": ""
    };
    return this;
  };
  this.reprompt = function(str) {
    if (typeof this.response.response.reprompt == "undefined") {
      this.response.response.reprompt = {
        "outputSpeech": {
          "type": "SSML",
          "ssml": SSML.fromStr(str)
        }
      };
    } else {
      // append str to the current outputSpeech, stripping the out speak tag
      this.response.response.reprompt.outputSpeech.ssml = SSML.fromStr(str, this.response.response.reprompt.outputSpeech.text);
    }
    return this;
  };
  this.card = function(oCard) {
    if (2 == arguments.length) {  //backwards compat
      oCard = {
        type: "Simple",
        title: arguments[0],
        content: arguments[1]
      };
    }

    var requiredAttrs = [],
      clenseAttrs = [];

    switch (oCard.type) {
      case 'Simple':
        requiredAttrs.push('content');
        clenseAttrs.push('content');
        break;
      case 'Standard':
        requiredAttrs.push('text');
        clenseAttrs.push('text');
        if (('image' in oCard) && ( !('smallImageUrl' in oCard['image']) && !('largeImageUrl' in oCard['image']) )) {
          console.error('If card.image is defined, must specify at least smallImageUrl or largeImageUrl');
          return this;
        }
        break;
      default:
        break;
    }

    var hasAllReq = requiredAttrs.every(function(idx) {
      if (!(idx in oCard)) {
        console.error('Card object is missing required attr "' + idx + '"');
        return false;
      }
      return true;
    });

    if (!hasAllReq) {
      return this;
    }

    // remove all SSML to keep the card clean
    clenseAttrs.forEach(function(idx) {
      oCard[idx] = SSML.cleanse(oCard[idx]);
    });

    this.response.response.card = oCard;

    return this;
  };
  this.linkAccount = function() {
    this.response.response.card = {
      "type": "LinkAccount"
    };
    return this;
  };
  this.shouldEndSession = function(bool, reprompt) {
    this.response.response.shouldEndSession = bool;
    if (reprompt) {
      this.reprompt(reprompt);
    }
    return this;
  };
  this.session = function(key, val) {
    if (typeof val == "undefined") {
      return this.response.sessionAttributes[key];
    } else {
      this.response.sessionAttributes[key] = val;
    }
    return this;
  };
  this.clearSession = function(key) {
    if (typeof key == "string" && typeof this.response.sessionAttributes[key] != "undefined") {
      delete this.response.sessionAttributes[key];
    } else {
      this.response.sessionAttributes = {};
    }
    return this;
  };

};

alexa.request = function(json) {
  this.data = json;
  this.slot = function(slotName, defaultValue) {
    try {
      return this.data.request.intent.slots[slotName].value;
    } catch (e) {
      console.error("missing intent in request: " + slotName, e);
      return defaultValue;
    }
  };
  this.type = function() {
    console.log(this.data);
    try {
      return this.data.request.type;
    } catch (e) {
      console.error("missing type", e);
      return null;
    }
  };

  if (typeof this.data.session == 'undefined') {
    this.sessionDetails = {
      "new": false,
      "sessionId": null,
      "userId": null,
      "accessToken": null,
      "attributes": {},
      "application": {
        applicationId: null
      }
    };
  } else {
    this.sessionDetails = {
      "new": this.data.session.new,
      "sessionId": this.data.session.sessionId,
      "userId": this.data.session.user.userId,
      "accessToken": this.data.session.user.accessToken || null,
      "attributes": this.data.session.attributes,
      "application": this.data.session.application
    };
  }
  this.userId = this.sessionDetails.userId;
  this.applicationId = this.sessionDetails.application.applicationId;
  this.sessionId = this.sessionDetails.sessionId;
  this.sessionAttributes = this.sessionDetails.attributes;
  this.isSessionNew = (true === this.sessionDetails.new);
  this.session = function(key) {
    try {
      return this.sessionDetails.attributes[key];
    } catch (e) {
      console.error("key not found on session attributes: " + key, e);
      return;
    }
  };
};

alexa.apps = {};

alexa.app = function(name, endpoint) {
  var self = this;
  this.name = name;
  this.messages = {
    // When an intent was passed in that the application was not configured to handle
    "NO_INTENT_FOUND": "Sorry, the application didn't know what to do with that intent",
    // When the app was used with 'open' or 'launch' but no launch handler was defined
    "NO_LAUNCH_FUNCTION": "Try telling the application what to do instead of opening it",
    // When a request type was not recognized
    "INVALID_REQUEST_TYPE": "Error: not a valid request",
    // If some other exception happens
    "GENERIC_ERROR": "Sorry, the application encountered an error"
  };

  // Persist session variables from every request into every response?
  this.persistentSession = true;

  // use a minimal set of utterances or the full cartesian product?
  this.exhaustiveUtterances = false;

  // A catch-all error handler - do nothing by default
  this.error = null;

  // pre/post hooks to be run on every request
  this.pre = function(/*request, response, type*/) {};
  this.post = function(/*request, response, type*/) {};

  this.endpoint = endpoint;
  // A mapping of keywords to arrays of possible values, for expansion of sample utterances
  this.dictionary = {};
  this.intents = {};
  this.intent = function(intentName, schema, func) {
    if (typeof schema == "function") {
      func = schema;
      schema = null;
    }
    self.intents[intentName] = {
      "name": intentName,
      "function": func
    };
    if (schema) {
      self.intents[intentName].schema = schema;
    }
  };
  this.launchFunc = null;
  this.launch = function(func) {
    self.launchFunc = func;
  };
  this.sessionEndedFunc = null;
  this.sessionEnded = function(func) {
    self.sessionEndedFunc = func;
  };
  this.request = function(request_json) {
    return new Promise(function(resolve, reject) {
      var request = new alexa.request(request_json);
      var response = new alexa.response();
      var postExecuted = false;
      // Attach Promise resolve/reject functions to the response object
      response.send = function(exception) {
        if (typeof self.post == "function" && !postExecuted) {
          postExecuted = true;
          self.post(request, response, requestType, exception);
        }
        if (!response.resolved) {
          response.resolved = true;
          resolve(response.response);
        }
      };
      response.fail = function(msg, exception) {
        if (typeof self.post == "function" && !postExecuted) {
          postExecuted = true;
          self.post(request, response, requestType, exception);
        }
        if (!response.resolved) {
          response.resolved = true;
          reject(msg);
        }
      };
      try {
        var key;
        // Copy all the session attributes from the request into the response so they persist.
        // The Alexa API doesn't think session variables should persist for the entire
        // duration of the session, but I do.
        if (request.sessionAttributes && self.persistentSession) {
          for (key in request.sessionAttributes) {
            response.session(key, request.sessionAttributes[key]);
          }
        }
        var requestType = request.type();
        console.log('Request type: ' + requestType);
        if (typeof self.pre == "function") {
          self.pre(request, response, requestType);
        }
        if (!response.resolved) {
          if ("IntentRequest" === requestType || "Messaging.MessageReceived" === requestType) {
            if ("IntentRequest" === requestType) {
            var intent = request_json.request.intent.name;
            if (typeof self.intents[intent] != "undefined" && typeof self.intents[intent]["function"] == "function") {
              if (false !== self.intents[intent]["function"](request, response)) {
                response.send();
              }
            } else {
              throw "NO_INTENT_FOUND";
            }
          } else if ("LaunchRequest" === requestType) {
            if (typeof self.launchFunc == "function") {
              if (false !== self.launchFunc(request, response)) {
                response.send();
              }
            } else {
              throw "NO_LAUNCH_FUNCTION";
            }
          } else if ("SessionEndedRequest" === requestType) {
            if (typeof self.sessionEndedFunc == "function") {
              if (false !== self.sessionEndedFunc(request, response)) {
                response.send();
              }
            }
          } else {
            throw "INVALID_REQUEST_TYPE";
          }
        }
      } catch (e) {
        if (typeof self.error == "function") {
          self.error(e, request, response);
        } else if (typeof e == "string" && self.messages[e]) {
          response.say(self.messages[e]);
          response.send(e);
        }
        if (!response.resolved) {
          response.fail("Unhandled exception" + e.message, e);
        }
      }
    });
  };

  // Extract the schema and generate a schema JSON object
  this.schema = function() {
    var schema = {
      "intents": []
    }, intentName, intent, key;
    for (intentName in self.intents) {
      intent = self.intents[intentName];
      var intentSchema = {
        "intent": intent.name,
        "slots": []
      };
      if (intent.schema) {
        if (intent.schema.slots) {
          for (key in intent.schema.slots) {
            intentSchema.slots.push({
              "name": key,
              "type": intent.schema.slots[key]
            });
          }
        }
      }
      schema.intents.push(intentSchema);
    }
    return JSON.stringify(schema, null, 3);
  };

  // Generate a list of sample utterances
  this.utterances = function() {
    var intentName,
      intent,
      out = "";
    for (intentName in self.intents) {
      intent = self.intents[intentName];
      if (intent.schema && intent.schema.utterances) {
        intent.schema.utterances.forEach(function(sample) {
          var list = AlexaUtterances(sample,
            intent.schema.slots,
            self.dictionary,
            self.exhaustiveUtterances);
          list.forEach(function(utterance) {
            out += intent.name + "\t" + (utterance.replace(/\s+/g, " ")).trim() + "\n";
          });
        });
      }
    }
    return out;
  };

  // A built-in handler for AWS Lambda
  this.handler = function(event, context) {
    self.request(event)
      .then(function(response) {
        context.succeed(response);
      })
      .catch(function(response) {
        context.fail(response);
      });
  };

  // For backwards compatibility
  this.lambda = function() {
    return self.handler;
  };

  // A utility method to bootstrap alexa endpoints into express automatically
  this.express = function(express, path, enableDebug) {
    var endpoint = (path || "/") + (self.endpoint || self.name);
    express.post(endpoint, function(req, res) {
      self.request(req.body).then(function(response) {
        res.json(response);
      }, function() {
        res.status(500).send("Server Error");
      });
    });
    if (typeof enableDebug != "boolean") {
      enableDebug = true;
    }
    if (enableDebug) {
      express.get(endpoint, function(req, res) {
        res.render("test", {
          "json": self,
          "schema": self.schema(),
          "utterances": self.utterances()
        });
      });
    }
  };

  // Add the app to the global list of named apps
  if (name) {
    alexa.apps[name] = self;
  }

  return this;
};

module.exports = alexa;
<commit_msg>fix syntax error<commit_after>var Promise = require("bluebird");
var AlexaUtterances = require("alexa-utterances");
var SSML = require("./to-ssml");
var alexa = {};

alexa.response = function() {
  this.resolved = false;
  this.response = {
    "version": "1.0",
    "sessionAttributes": {},
    "response": {
      "shouldEndSession": true
    }
  };
  this.say = function(str) {
    if (typeof this.response.response.outputSpeech == "undefined") {
      this.response.response.outputSpeech = {
        "type": "SSML",
        "ssml": SSML.fromStr(str)
      };
    } else {
      // append str to the current outputSpeech, stripping the out speak tag
      this.response.response.outputSpeech.ssml = SSML.fromStr(str, this.response.response.outputSpeech.ssml);
    }
    return this;
  };
  this.clear = function(/*str*/) {
    this.response.response.outputSpeech = {
      "type": "PlainText",
      "text": ""
    };
    return this;
  };
  this.reprompt = function(str) {
    if (typeof this.response.response.reprompt == "undefined") {
      this.response.response.reprompt = {
        "outputSpeech": {
          "type": "SSML",
          "ssml": SSML.fromStr(str)
        }
      };
    } else {
      // append str to the current outputSpeech, stripping the out speak tag
      this.response.response.reprompt.outputSpeech.ssml = SSML.fromStr(str, this.response.response.reprompt.outputSpeech.text);
    }
    return this;
  };
  this.card = function(oCard) {
    if (2 == arguments.length) {  //backwards compat
      oCard = {
        type: "Simple",
        title: arguments[0],
        content: arguments[1]
      };
    }

    var requiredAttrs = [],
      clenseAttrs = [];

    switch (oCard.type) {
      case 'Simple':
        requiredAttrs.push('content');
        clenseAttrs.push('content');
        break;
      case 'Standard':
        requiredAttrs.push('text');
        clenseAttrs.push('text');
        if (('image' in oCard) && ( !('smallImageUrl' in oCard['image']) && !('largeImageUrl' in oCard['image']) )) {
          console.error('If card.image is defined, must specify at least smallImageUrl or largeImageUrl');
          return this;
        }
        break;
      default:
        break;
    }

    var hasAllReq = requiredAttrs.every(function(idx) {
      if (!(idx in oCard)) {
        console.error('Card object is missing required attr "' + idx + '"');
        return false;
      }
      return true;
    });

    if (!hasAllReq) {
      return this;
    }

    // remove all SSML to keep the card clean
    clenseAttrs.forEach(function(idx) {
      oCard[idx] = SSML.cleanse(oCard[idx]);
    });

    this.response.response.card = oCard;

    return this;
  };
  this.linkAccount = function() {
    this.response.response.card = {
      "type": "LinkAccount"
    };
    return this;
  };
  this.shouldEndSession = function(bool, reprompt) {
    this.response.response.shouldEndSession = bool;
    if (reprompt) {
      this.reprompt(reprompt);
    }
    return this;
  };
  this.session = function(key, val) {
    if (typeof val == "undefined") {
      return this.response.sessionAttributes[key];
    } else {
      this.response.sessionAttributes[key] = val;
    }
    return this;
  };
  this.clearSession = function(key) {
    if (typeof key == "string" && typeof this.response.sessionAttributes[key] != "undefined") {
      delete this.response.sessionAttributes[key];
    } else {
      this.response.sessionAttributes = {};
    }
    return this;
  };

};

alexa.request = function(json) {
  this.data = json;
  this.slot = function(slotName, defaultValue) {
    try {
      return this.data.request.intent.slots[slotName].value;
    } catch (e) {
      console.error("missing intent in request: " + slotName, e);
      return defaultValue;
    }
  };
  this.type = function() {
    console.log(this.data);
    try {
      return this.data.request.type;
    } catch (e) {
      console.error("missing type", e);
      return null;
    }
  };

  if (typeof this.data.session == 'undefined') {
    this.sessionDetails = {
      "new": false,
      "sessionId": null,
      "userId": null,
      "accessToken": null,
      "attributes": {},
      "application": {
        applicationId: null
      }
    };
  } else {
    this.sessionDetails = {
      "new": this.data.session.new,
      "sessionId": this.data.session.sessionId,
      "userId": this.data.session.user.userId,
      "accessToken": this.data.session.user.accessToken || null,
      "attributes": this.data.session.attributes,
      "application": this.data.session.application
    };
  }
  this.userId = this.sessionDetails.userId;
  this.applicationId = this.sessionDetails.application.applicationId;
  this.sessionId = this.sessionDetails.sessionId;
  this.sessionAttributes = this.sessionDetails.attributes;
  this.isSessionNew = (true === this.sessionDetails.new);
  this.session = function(key) {
    try {
      return this.sessionDetails.attributes[key];
    } catch (e) {
      console.error("key not found on session attributes: " + key, e);
      return;
    }
  };
};

alexa.apps = {};

alexa.app = function(name, endpoint) {
  var self = this;
  this.name = name;
  this.messages = {
    // When an intent was passed in that the application was not configured to handle
    "NO_INTENT_FOUND": "Sorry, the application didn't know what to do with that intent",
    // When the app was used with 'open' or 'launch' but no launch handler was defined
    "NO_LAUNCH_FUNCTION": "Try telling the application what to do instead of opening it",
    // When a request type was not recognized
    "INVALID_REQUEST_TYPE": "Error: not a valid request",
    // If some other exception happens
    "GENERIC_ERROR": "Sorry, the application encountered an error"
  };

  // Persist session variables from every request into every response?
  this.persistentSession = true;

  // use a minimal set of utterances or the full cartesian product?
  this.exhaustiveUtterances = false;

  // A catch-all error handler - do nothing by default
  this.error = null;

  // pre/post hooks to be run on every request
  this.pre = function(/*request, response, type*/) {};
  this.post = function(/*request, response, type*/) {};

  this.endpoint = endpoint;
  // A mapping of keywords to arrays of possible values, for expansion of sample utterances
  this.dictionary = {};
  this.intents = {};
  this.intent = function(intentName, schema, func) {
    if (typeof schema == "function") {
      func = schema;
      schema = null;
    }
    self.intents[intentName] = {
      "name": intentName,
      "function": func
    };
    if (schema) {
      self.intents[intentName].schema = schema;
    }
  };
  this.launchFunc = null;
  this.launch = function(func) {
    self.launchFunc = func;
  };
  this.sessionEndedFunc = null;
  this.sessionEnded = function(func) {
    self.sessionEndedFunc = func;
  };
  this.request = function(request_json) {
    return new Promise(function(resolve, reject) {
      var request = new alexa.request(request_json);
      var response = new alexa.response();
      var postExecuted = false;
      // Attach Promise resolve/reject functions to the response object
      response.send = function(exception) {
        if (typeof self.post == "function" && !postExecuted) {
          postExecuted = true;
          self.post(request, response, requestType, exception);
        }
        if (!response.resolved) {
          response.resolved = true;
          resolve(response.response);
        }
      };
      response.fail = function(msg, exception) {
        if (typeof self.post == "function" && !postExecuted) {
          postExecuted = true;
          self.post(request, response, requestType, exception);
        }
        if (!response.resolved) {
          response.resolved = true;
          reject(msg);
        }
      };
      try {
        var key;
        // Copy all the session attributes from the request into the response so they persist.
        // The Alexa API doesn't think session variables should persist for the entire
        // duration of the session, but I do.
        if (request.sessionAttributes && self.persistentSession) {
          for (key in request.sessionAttributes) {
            response.session(key, request.sessionAttributes[key]);
          }
        }
        var requestType = request.type();
        console.log('Request type: ' + requestType);
        if (typeof self.pre == "function") {
          self.pre(request, response, requestType);
        }
        if (!response.resolved) {
          if ("IntentRequest" === requestType || "Messaging.MessageReceived" === requestType) {
            var intent = request_json.request.intent.name;
            if (typeof self.intents[intent] != "undefined" && typeof self.intents[intent]["function"] == "function") {
              if (false !== self.intents[intent]["function"](request, response)) {
                response.send();
              }
            } else {
              throw "NO_INTENT_FOUND";
            }
          } else if ("LaunchRequest" === requestType) {
            if (typeof self.launchFunc == "function") {
              if (false !== self.launchFunc(request, response)) {
                response.send();
              }
            } else {
              throw "NO_LAUNCH_FUNCTION";
            }
          } else if ("SessionEndedRequest" === requestType) {
            if (typeof self.sessionEndedFunc == "function") {
              if (false !== self.sessionEndedFunc(request, response)) {
                response.send();
              }
            }
          } else {
            throw "INVALID_REQUEST_TYPE";
          }
        }
      } catch (e) {
        if (typeof self.error == "function") {
          self.error(e, request, response);
        } else if (typeof e == "string" && self.messages[e]) {
          response.say(self.messages[e]);
          response.send(e);
        }
        if (!response.resolved) {
          response.fail("Unhandled exception" + e.message, e);
        }
      }
    });
  };

  // Extract the schema and generate a schema JSON object
  this.schema = function() {
    var schema = {
      "intents": []
    }, intentName, intent, key;
    for (intentName in self.intents) {
      intent = self.intents[intentName];
      var intentSchema = {
        "intent": intent.name,
        "slots": []
      };
      if (intent.schema) {
        if (intent.schema.slots) {
          for (key in intent.schema.slots) {
            intentSchema.slots.push({
              "name": key,
              "type": intent.schema.slots[key]
            });
          }
        }
      }
      schema.intents.push(intentSchema);
    }
    return JSON.stringify(schema, null, 3);
  };

  // Generate a list of sample utterances
  this.utterances = function() {
    var intentName,
      intent,
      out = "";
    for (intentName in self.intents) {
      intent = self.intents[intentName];
      if (intent.schema && intent.schema.utterances) {
        intent.schema.utterances.forEach(function(sample) {
          var list = AlexaUtterances(sample,
            intent.schema.slots,
            self.dictionary,
            self.exhaustiveUtterances);
          list.forEach(function(utterance) {
            out += intent.name + "\t" + (utterance.replace(/\s+/g, " ")).trim() + "\n";
          });
        });
      }
    }
    return out;
  };

  // A built-in handler for AWS Lambda
  this.handler = function(event, context) {
    self.request(event)
      .then(function(response) {
        context.succeed(response);
      })
      .catch(function(response) {
        context.fail(response);
      });
  };

  // For backwards compatibility
  this.lambda = function() {
    return self.handler;
  };

  // A utility method to bootstrap alexa endpoints into express automatically
  this.express = function(express, path, enableDebug) {
    var endpoint = (path || "/") + (self.endpoint || self.name);
    express.post(endpoint, function(req, res) {
      self.request(req.body).then(function(response) {
        res.json(response);
      }, function() {
        res.status(500).send("Server Error");
      });
    });
    if (typeof enableDebug != "boolean") {
      enableDebug = true;
    }
    if (enableDebug) {
      express.get(endpoint, function(req, res) {
        res.render("test", {
          "json": self,
          "schema": self.schema(),
          "utterances": self.utterances()
        });
      });
    }
  };

  // Add the app to the global list of named apps
  if (name) {
    alexa.apps[name] = self;
  }

  return this;
};

module.exports = alexa;
<|endoftext|>


<commit_before>var showEvent = jasmine.createSpy('showEvent'),
    hideEvent = jasmine.createSpy('hideEvent'),
    readyEvent = jasmine.createSpy('readyEvent'),
    destroyEvent = jasmine.createSpy('destroyEvent'),
    changeLayoutEvent = jasmine.createSpy('changeLayoutEvent'),
    popover1 = $('#popover1').popover()
        .on('show', function () { showEvent(); })
        .on('hide', function () { hideEvent(); })
        .on('ready', function () { readyEvent(); }),
    popover2 = $('#popover2').popover({
        'shown': true,
        'addClass': 'test',
        'closable': false,
        'shownby': 'mouseenter'
    }),
    $trigger = popover1.$trigger;

describe('Popover', function () {

    it('should be defined on ch object', function () {
        expect(ch.hasOwnProperty('Popover')).toBeTruthy();
        expect(typeof ch.Popover).toEqual('function');
    });

    it('should be defined on $ object', function () {
        expect($.fn.hasOwnProperty('popover')).toBeTruthy();
        expect(typeof $.fn.popover).toEqual('function');
    });

    it('should return a new instance', function () {
        expect(popover1 instanceof ch.Popover).toBeTruthy();
    });

    it('should emit the "ready" event when it\'s ready', function () {
        waits(50);
        runs(function () {
            expect(readyEvent).toHaveBeenCalled();
        });
    });

    describe('should use the following abilities:', function () {
        it('EventEmitter', function () {
            expect(popover1.on).not.toEqual(undefined);
            expect(typeof popover1.on).toEqual('function');
        });

        it('Content', function () {
            expect(popover1.content).not.toEqual(undefined);
            expect(typeof popover1.content).toEqual('function');
        });

        it('Collapsible', function () {
            expect(popover1._show).not.toEqual(undefined);
            expect(typeof popover1._show).toEqual('function');
        });

        it('Positioner', function () {
            expect(popover1.position).not.toEqual(undefined);
            expect(typeof popover1.position).toEqual('object');
        });
    });
});

describe('It should have the following public properties:', function () {

    it('.$trigger', function () {
        expect(popover1.$trigger).not.toEqual(undefined);
        expect(popover1.$trigger instanceof $).toBeTruthy();
    });

    it('.$container', function () {
        expect(popover1.$container).not.toEqual(undefined);
        expect(popover1.$container instanceof $).toBeTruthy();
    });

    it('.name', function () {
        expect(popover1.name).not.toEqual(undefined);
        expect(typeof popover1.name).toEqual('string');
        expect(popover1.name).toEqual('popover');
    });

    it('.constructor', function () {
        expect(popover1.constructor).not.toEqual(undefined);
        expect(typeof popover1.constructor).toEqual('function');
    });

    it('.uid', function () {
        expect(popover1.uid).not.toEqual(undefined);
        expect(typeof popover1.uid).toEqual('number');
    });
});

describe('It should have the following public methods:', function () {
    var methods = ['init', 'destroy', 'show', 'hide', 'isShown', 'enable', 'disable'],
        i = 0,
        len = methods.length;

    for (i; i < len; i += 1) {
        (function (i){
            it('.' + methods[i] + '()', function () {
                expect(popover1[methods[i]]).not.toEqual(undefined);
                expect(typeof popover1[methods[i]]).toEqual('function');
            });
        }(i));
    }
});

describe('It should have a trigger that', function () {

    it('should exist', function () {
        expect($trigger).not.toEqual(undefined);
        expect($trigger[0].nodeType).toEqual(1);
        expect($trigger instanceof $).toBeTruthy();
    });

    describe('should have the following WAI-ARIA roles and properties:', function () {

        it('role: button', function () {
            expect($trigger.attr('role')).toEqual('button');
        });

        it('aria-owns: the id of the popup element', function () {
            expect($trigger.attr('aria-owns')).toEqual('ch-popover-1');
        });

        it('haspopup: true', function () {
            expect($trigger.attr('aria-haspopup')).toEqual('true');
        });
    });
});

describe('Its show() method', function () {

    it('should add "ch-popover-trigger-on" class name to trigger', function () {

        expect($trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();

        popover1.show();

        expect($trigger.hasClass('ch-popover-trigger-on')).toBeTruthy();

        popover1.hide();
    });

	describe('should create an element at the bottom of body that', function () {

		var $container = popover1.$container,
            $close = $container.children(':first');

		it('be the same than the $container property', function () {
            expect($(document.body).children().last().attr('id')).toEqual($container.attr('id'));
		});

		it('should have the same ID than the "aria-owns" attribute', function () {
			expect($container.attr('id')).toEqual($trigger.attr('aria-owns'));
		});

		it('should have the ARIA role "dialog"', function () {
			expect($container.attr('role')).toEqual('dialog');
		});

		it('should have the "ch-popover" classname', function () {
            expect($container.hasClass('ch-popover')).toBeTruthy();
		});

		it('should have a child with the "ch-popover-content" classname', function () {
			expect($container.children().eq(1).hasClass('ch-popover-content')).toBeTruthy();
		});

        describe('have a close button', function () {
            it('with the "ch-close" classname', function () {
                expect($close.hasClass('ch-close')).toBeTruthy();
            });

            it('with the role "button"', function () {
                expect($close.attr('role')).toEqual('button');
            });

            it('with the ARIA label "Close"', function () {
                expect($close.attr('aria-label')).toEqual('Close');
            });
        });
	});

    it('should emit the "show" event', function () {
        expect(showEvent).toHaveBeenCalled();
    });

    it('should return the same instance than initialized widget', function () {
        expect(popover1.show()).toEqual(popover1);
    });
});

describe('Its hide() method', function () {

    it('should remove "ch-popover-trigger-on" class name to trigger', function () {
        popover1.show().hide();
        expect($trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();
    });

	it('should delete the element at the bottom of body', function () {

		var flag = false;

		expect($(document.body).children().last().attr('id')).toEqual($trigger.attr('aria-owns'));

		popover1.hide();

		waits(500);

		runs(function () {
			expect($(document.body).children().last().attr('id')).not.toEqual($trigger.attr('aria-owns'));
		});
	});

    it('should emit the "hide" event', function () {
        expect(hideEvent).toHaveBeenCalled();
    });

	it('should return the same instance than initialized widget', function () {
		expect(popover1.hide()).toEqual(popover1);
	});
});

describe('Its isShown() method', function () {

    it('should return a boolean value', function () {
        expect(typeof popover1.isShown()).toEqual('boolean');
    });

    it('should return "true" when the widget is shown', function () {
        expect(popover1.show().isShown()).toBeTruthy();
    });

    it('should return "false" when the widget is hidden', function () {
        expect(popover1.hide().isShown()).toBeFalsy();
    });
});

describe('Its disable() method', function () {

    it('should return the same instance than initialized widget', function () {
        expect(popover1.disable()).toEqual(popover1);
    });

    it('should prevent to show its container', function () {
        expect(popover1.disable().show().$trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();
    });
});

describe('Its enable() method', function () {

    it('should return the same instance than initialized widget', function () {
        expect(popover1.enable()).toEqual(popover1);
    });

    it('should allow to show its container', function () {
        expect(popover1.enable().show().$trigger.hasClass('ch-popover-trigger-on')).toBeTruthy();
    });
});

describe('Its width() method', function () {

	beforeEach(function () {
		popover1.show();
	});

	it('as a getter should return the width of the floated object', function () {
		expect(popover1.width()).toEqual(popover1.$container[0].style.width);
	});

	describe('as a setter', function () {

        it('should change the width of the floated object', function () {

    		var w = 123;

    		expect(popover1.width()).not.toEqual(w);

    		popover1.width(w);

            expect(popover1.width()).toEqual(w);
        });

        it('should return the same instance than initialized widget', function () {
            expect(popover1.width(321)).toEqual(popover1);
        });
	});
});

describe('Its height() method', function () {

    beforeEach(function () {
        popover1.show();
    });

    it('as a getter should return the height of the floated object', function () {
        expect(popover1.height()).toEqual(popover1.$container[0].style.height);
    });

    describe('as a setter', function () {

        it('should change the height of the floated object', function () {

            var h = 55;

            expect(popover1.height()).not.toEqual(h);

            popover1.height(h);

            expect(popover1.height()).toEqual(h);
        });

        it('should return the same instance than initialized widget', function () {
            expect(popover1.height(321)).toEqual(popover1);
        });
    });
});

describe('Instance a Popover configured', function () {

    it('open by default', function () {
        expect(popover2.isShown()).toBeTruthy();
    });

	it('with custom class names should contain the specified class names in its container', function () {
		expect(popover2.$container.hasClass('ch-popover')).toBeTruthy();
		expect(popover2.$container.hasClass('test')).toBeTruthy();
	});

	describe('with "shownby" preference:', function () {

        it('"click" should give a pointer cursor to the trigger', function () {
            expect(popover1._$el.hasClass('ch-shownby-click')).toBeTruthy();
            expect(popover1._$el.hasClass('ch-shownby-mouseenter')).toBeFalsy();
        });

        it('"mouseenter" should give a default cursor to the trigger', function () {
			expect(popover2._$el.hasClass('ch-shownby-mouseenter')).toBeTruthy();
            expect(popover2._$el.hasClass('ch-shownby-click')).toBeFalsy();
		});
	});
});

describe('Its destroy() method', function () {

    beforeEach(function () {
        if (popover2) {
            popover2.destroy();
            popover2 = undefined;
        }
    });

    it('should reset the $trigger', function () {

        var $t = $('#popover2');

        expect($t.hasClass('ch-popover-trigger')).toBeFalsy();
        expect($t.attr('data-title')).toBeUndefined();
        expect($t.attr('aria-owns')).toBeUndefined();
        expect($t.attr('aria-haspopup')).toBeUndefined();
        expect($t.attr('data-side')).toBeUndefined();
        expect($t.attr('data-align')).toBeUndefined();
        expect($t.attr('role')).toBeUndefined();
    });

    it('should remove ".popover" events', function () {
        expect($._data($('#popover2')[0], 'events')).toBeUndefined();
    });

    it('should remove the instance from the element', function () {
        expect($('#popover2').data('popover')).toBeUndefined();
    });

    it('should emit the "destroy" event', function () {
        expect(destroyEvent).toHaveBeenCalled();
    });
});<commit_msg>GH-987 Popover: add tests for popovers without trigger.<commit_after>var showEvent = jasmine.createSpy('showEvent'),
    hideEvent = jasmine.createSpy('hideEvent'),
    readyEvent = jasmine.createSpy('readyEvent'),
    destroyEvent = jasmine.createSpy('destroyEvent'),
    changeLayoutEvent = jasmine.createSpy('changeLayoutEvent'),
    popover1 = $('#popover1').popover()
        .on('show', function () { showEvent(); })
        .on('hide', function () { hideEvent(); })
        .on('ready', function () { readyEvent(); }),
    $trigger = popover1.$trigger,
    popover2 = $('#popover2').popover({
        'shown': true,
        'addClass': 'test',
        'closable': false,
        'shownby': 'mouseenter'
    }),
    popover3 = $.popover();

describe('Popover', function () {

    it('should be defined on ch object', function () {
        expect(ch.hasOwnProperty('Popover')).toBeTruthy();
        expect(typeof ch.Popover).toEqual('function');
    });

    it('should be defined on $ object', function () {
        expect($.fn.hasOwnProperty('popover')).toBeTruthy();
        expect(typeof $.fn.popover).toEqual('function');
    });

    it('should return a new instance', function () {
        expect(popover1 instanceof ch.Popover).toBeTruthy();
    });

    it('should emit the "ready" event when it\'s ready', function () {
        waits(50);
        runs(function () {
            expect(readyEvent).toHaveBeenCalled();
        });
    });

    describe('should use the following abilities:', function () {
        it('EventEmitter', function () {
            expect(popover1.on).not.toEqual(undefined);
            expect(typeof popover1.on).toEqual('function');
        });

        it('Content', function () {
            expect(popover1.content).not.toEqual(undefined);
            expect(typeof popover1.content).toEqual('function');
        });

        it('Collapsible', function () {
            expect(popover1._show).not.toEqual(undefined);
            expect(typeof popover1._show).toEqual('function');
        });

        it('Positioner', function () {
            expect(popover1.position).not.toEqual(undefined);
            expect(typeof popover1.position).toEqual('object');
        });
    });
});

describe('It should have the following public properties:', function () {

    it('.$trigger', function () {
        expect(popover1.$trigger).not.toEqual(undefined);
        expect(popover1.$trigger instanceof $).toBeTruthy();
    });

    it('.$container', function () {
        expect(popover1.$container).not.toEqual(undefined);
        expect(popover1.$container instanceof $).toBeTruthy();
    });

    it('.name', function () {
        expect(popover1.name).not.toEqual(undefined);
        expect(typeof popover1.name).toEqual('string');
        expect(popover1.name).toEqual('popover');
    });

    it('.constructor', function () {
        expect(popover1.constructor).not.toEqual(undefined);
        expect(typeof popover1.constructor).toEqual('function');
    });

    it('.uid', function () {
        expect(popover1.uid).not.toEqual(undefined);
        expect(typeof popover1.uid).toEqual('number');
    });
});

describe('It should have the following public methods:', function () {
    var methods = ['init', 'destroy', 'show', 'hide', 'isShown', 'enable', 'disable'],
        i = 0,
        len = methods.length;

    for (i; i < len; i += 1) {
        (function (i){
            it('.' + methods[i] + '()', function () {
                expect(popover1[methods[i]]).not.toEqual(undefined);
                expect(typeof popover1[methods[i]]).toEqual('function');
            });
        }(i));
    }
});

describe('It should have a trigger that', function () {

    it('should exist', function () {
        expect($trigger).not.toEqual(undefined);
        expect($trigger[0].nodeType).toEqual(1);
        expect($trigger instanceof $).toBeTruthy();
    });

    describe('should have the following WAI-ARIA roles and properties:', function () {

        it('aria-owns: the id of the popup element', function () {
            expect($trigger.attr('aria-owns')).toEqual('ch-popover-1');
        });

        it('haspopup: true', function () {
            expect($trigger.attr('aria-haspopup')).toEqual('true');
        });
    });
});

describe('Its show() method', function () {

    it('should add "ch-popover-trigger-on" class name to trigger', function () {

        expect($trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();

        popover1.show();

        expect($trigger.hasClass('ch-popover-trigger-on')).toBeTruthy();

        popover1.hide();
    });

	describe('should create an element at the bottom of body that', function () {

		var $container = popover1.$container,
            $close = $container.children(':first');

		it('be the same than the $container property', function () {
            expect($(document.body).children().last().attr('id')).toEqual($container.attr('id'));
		});

		it('should have the same ID than the "aria-owns" attribute', function () {
			expect($container.attr('id')).toEqual($trigger.attr('aria-owns'));
		});

		it('should have the ARIA role "dialog"', function () {
			expect($container.attr('role')).toEqual('dialog');
		});

		it('should have the "ch-popover" classname', function () {
            expect($container.hasClass('ch-popover')).toBeTruthy();
		});

		it('should have a child with the "ch-popover-content" classname', function () {
			expect($container.children().eq(1).hasClass('ch-popover-content')).toBeTruthy();
		});

        describe('have a close button', function () {
            it('with the "ch-close" classname', function () {
                expect($close.hasClass('ch-close')).toBeTruthy();
            });

            it('with the role "button"', function () {
                expect($close.attr('role')).toEqual('button');
            });

            it('with the ARIA label "Close"', function () {
                expect($close.attr('aria-label')).toEqual('Close');
            });
        });
	});

    it('should emit the "show" event', function () {
        expect(showEvent).toHaveBeenCalled();
    });

    it('should return the same instance than initialized widget', function () {
        expect(popover1.show()).toEqual(popover1);
    });
});

describe('Its hide() method', function () {

    it('should remove "ch-popover-trigger-on" class name to trigger', function () {
        popover1.show().hide();
        expect($trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();
    });

	it('should delete the element at the bottom of body', function () {

		var flag = false;

		expect($(document.body).children().last().attr('id')).toEqual($trigger.attr('aria-owns'));

		popover1.hide();

		waits(500);

		runs(function () {
			expect($(document.body).children().last().attr('id')).not.toEqual($trigger.attr('aria-owns'));
		});
	});

    it('should emit the "hide" event', function () {
        expect(hideEvent).toHaveBeenCalled();
    });

	it('should return the same instance than initialized widget', function () {
		expect(popover1.hide()).toEqual(popover1);
	});
});

describe('Its isShown() method', function () {

    it('should return a boolean value', function () {
        expect(typeof popover1.isShown()).toEqual('boolean');
    });

    it('should return "true" when the widget is shown', function () {
        expect(popover1.show().isShown()).toBeTruthy();
    });

    it('should return "false" when the widget is hidden', function () {
        expect(popover1.hide().isShown()).toBeFalsy();
    });
});

describe('Its disable() method', function () {

    it('should return the same instance than initialized widget', function () {
        expect(popover1.disable()).toEqual(popover1);
    });

    it('should prevent to show its container', function () {
        expect(popover1.disable().show().$trigger.hasClass('ch-popover-trigger-on')).toBeFalsy();
    });
});

describe('Its enable() method', function () {

    it('should return the same instance than initialized widget', function () {
        expect(popover1.enable()).toEqual(popover1);
    });

    it('should allow to show its container', function () {
        expect(popover1.enable().show().$trigger.hasClass('ch-popover-trigger-on')).toBeTruthy();
    });
});

describe('Its width() method', function () {

	beforeEach(function () {
		popover1.show();
	});

	it('as a getter should return the width of the floated object', function () {
		expect(popover1.width()).toEqual(popover1.$container[0].style.width);
	});

	describe('as a setter', function () {

        it('should change the width of the floated object', function () {

    		var w = 123;

    		expect(popover1.width()).not.toEqual(w);

    		popover1.width(w);

            expect(popover1.width()).toEqual(w);
        });

        it('should return the same instance than initialized widget', function () {
            expect(popover1.width(321)).toEqual(popover1);
        });
	});
});

describe('Its height() method', function () {

    beforeEach(function () {
        popover1.show();
    });

    it('as a getter should return the height of the floated object', function () {
        expect(popover1.height()).toEqual(popover1.$container[0].style.height);
    });

    describe('as a setter', function () {

        it('should change the height of the floated object', function () {

            var h = 55;

            expect(popover1.height()).not.toEqual(h);

            popover1.height(h);

            expect(popover1.height()).toEqual(h);
        });

        it('should return the same instance than initialized widget', function () {
            expect(popover1.height(321)).toEqual(popover1);
        });
    });
});

describe('Instance a Popover configured', function () {

    it('open by default', function () {
        expect(popover2.isShown()).toBeTruthy();
    });

	it('with custom class names should contain the specified class names in its container', function () {
		expect(popover2.$container.hasClass('ch-popover')).toBeTruthy();
		expect(popover2.$container.hasClass('test')).toBeTruthy();
	});

	describe('with "shownby" preference:', function () {

        it('"click" should give a pointer cursor to the trigger', function () {
            expect(popover1._$el.hasClass('ch-shownby-click')).toBeTruthy();
            expect(popover1._$el.hasClass('ch-shownby-mouseenter')).toBeFalsy();
        });

        it('"mouseenter" should give a default cursor to the trigger', function () {
			expect(popover2._$el.hasClass('ch-shownby-mouseenter')).toBeTruthy();
            expect(popover2._$el.hasClass('ch-shownby-click')).toBeFalsy();
		});
	});
});

describe('Instance a Popover without trigger', function () {

    it('should be defined on $ object', function () {
        expect($.hasOwnProperty('popover')).toBeTruthy();
        expect(typeof $.popover).toEqual('function');
    });

    it('should return a new instance', function () {
        expect(popover3 instanceof ch.Popover).toBeTruthy();
    });

    it('shouldn\'t have .$trigger', function () {
        expect(popover3.$trigger).toBeUndefined();
    });
});

describe('Its destroy() method', function () {

    beforeEach(function () {
        if (popover2) {
            popover2.destroy();
            popover2 = undefined;
        }
    });

    it('should reset the $trigger', function () {

        var $t = $('#popover2');

        expect($t.hasClass('ch-popover-trigger')).toBeFalsy();
        expect($t.attr('data-title')).toBeUndefined();
        expect($t.attr('aria-owns')).toBeUndefined();
        expect($t.attr('aria-haspopup')).toBeUndefined();
        expect($t.attr('data-side')).toBeUndefined();
        expect($t.attr('data-align')).toBeUndefined();
        expect($t.attr('role')).toBeUndefined();
    });

    it('should remove ".popover" events', function () {
        expect($._data($('#popover2')[0], 'events')).toBeUndefined();
    });

    it('should remove the instance from the element', function () {
        expect($('#popover2').data('popover')).toBeUndefined();
    });

    it('should emit the "destroy" event', function () {
        expect(destroyEvent).toHaveBeenCalled();
    });
});<|endoftext|>


<commit_before>module.exports = MockIncomingMessage;

var Transform = require('stream').Transform,
	util = require('util');

function MockIncomingMessage(options) {
	var self = this;
	options = options || {};

	Transform.call(this);
	this._writableState.objectMode = true;
	this._readableState.objectMode = false;

	// Copy unreserved options
	var reservedOptions = [
		'method',
		'url',
		'headers',
		'rawHeaders'
	];

	Object.keys(options).forEach(function(key) {
		if (reservedOptions.indexOf(key) === -1)
			self[key] = options[key];
	});

	this.method = options.method || 'GET';
	this.url = options.url || '';

	// Set header names
	this.headers = {};
	this.rawHeaders = [];
	if (options.headers)
		Object.keys(options.headers).forEach(function(key) {
			// Only if the value is defined
			if( typeof options.headers[key] !== 'undefined' ) {

				// Convert it to string if not
				if (typeof headerVal !== 'string') {

					options.headers[key] += '';
				}

				var val = options.headers[key].toString();
				self.headers[key.toLowerCase()] = val;
				self.rawHeaders.push(key);
				self.rawHeaders.push(val);
			}
		});

	// Auto-end when no body
	if (this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE')
		this.end();
}

util.inherits(MockIncomingMessage, Transform);

MockIncomingMessage.prototype._transform = function(chunk, encoding, next) {
	if (this._failError)
		return this.emit('error', this._failError);

	if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk))
		chunk = JSON.stringify(chunk);

	this.push(chunk);

	next();
};

// Causes the request to emit an error when the body is read.
MockIncomingMessage.prototype._fail = function(error) {
	this._failError = error;
};
<commit_msg>Cleaned up pull request<commit_after>module.exports = MockIncomingMessage;

var Transform = require('stream').Transform,
	util = require('util');

function MockIncomingMessage(options) {
	var self = this;
	options = options || {};

	Transform.call(this);
	this._writableState.objectMode = true;
	this._readableState.objectMode = false;

	// Copy unreserved options
	var reservedOptions = [
		'method',
		'url',
		'headers',
		'rawHeaders'
	];

	Object.keys(options).forEach(function(key) {
		if (reservedOptions.indexOf(key) === -1)
			self[key] = options[key];
	});

	this.method = options.method || 'GET';
	this.url = options.url || '';

	// Set header names
	this.headers = {};
	this.rawHeaders = [];
	if (options.headers)
		Object.keys(options.headers).forEach(function(key) {
			var val = options.headers[key];

			if(val !== undefined) {
				if (typeof val !== 'string') {
					val += '';
				}

				self.headers[key.toLowerCase()] = val;
				self.rawHeaders.push(key);
				self.rawHeaders.push(val);
			}
		});

	// Auto-end when no body
	if (this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE')
		this.end();
}

util.inherits(MockIncomingMessage, Transform);

MockIncomingMessage.prototype._transform = function(chunk, encoding, next) {
	if (this._failError)
		return this.emit('error', this._failError);

	if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk))
		chunk = JSON.stringify(chunk);

	this.push(chunk);

	next();
};

// Causes the request to emit an error when the body is read.
MockIncomingMessage.prototype._fail = function(error) {
	this._failError = error;
};
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import classNames from 'classnames'

class Dropdown extends Component {
  constructor (props) {
    super(props)
    this.state = {
      selected: props.value || {
        label: props.placeholder || 'Select...',
        value: ''
      },
      isOpen: false
    }
    this.mounted = true
    this.handleDocumentClick = this.handleDocumentClick.bind(this)
    this.fireChangeEvent = this.fireChangeEvent.bind(this)
  }

  componentWillReceiveProps (newProps) {
    if (newProps.value && newProps.value !== this.state.selected) {
      this.setState({selected: newProps.value})
    } else if (newProps.placeholder) {
      this.setState({selected: { label: newProps.placeholder, value: '' }})
    }
  }

  componentDidMount () {
    document.addEventListener('click', this.handleDocumentClick, false)
  }

  componentWillUnmount () {
    this.mounted = false
    document.removeEventListener('click', this.handleDocumentClick, false)
  }

  handleMouseDown (event) {
    if (event.type === 'mousedown' && event.button !== 0) return
    event.stopPropagation()
    event.preventDefault()

    this.setState({
      isOpen: !this.state.isOpen
    })
  }

  setValue (value, label) {
    let newState = {
      selected: {
        value,
        label
      },
      isOpen: false
    }
    this.fireChangeEvent(newState)
    this.setState(newState)
  }

  fireChangeEvent (newState) {
    if (newState.selected !== this.state.selected && this.props.onChange) {
      this.props.onChange(newState.selected)
    }
  }

  renderOption (option) {
    let optionClass = classNames({
      [`${this.props.baseClassName}-option`]: true,
      'is-selected': option === this.state.selected
    })

    let value = option.value || option.label || option
    let label = option.label || option.value || option

    return (

        {label}

    )
  }

  buildMenu () {
    let { options, baseClassName } = this.props
    let ops = options.map((option) => {
      if (option.type === 'group') {
        let groupTitle = (<div className={`${baseClassName}-title`}>{option.name}
)
        let _options = option.items.map((item) => this.renderOption(item))

        return (
          <div className={`${baseClassName}-group`} key={option.name}>
            {groupTitle}
            {_options}

        )
      } else {
        return this.renderOption(option)
      }
    })

    return ops.length ? ops : <div className={`${baseClassName}-noresults`}>No options found

  }

  handleDocumentClick (event) {
    if (this.mounted) {
      if (!ReactDOM.findDOMNode(this).contains(event.target)) {
        this.setState({ isOpen: false })
      }
    }
  }

  render () {
    const { baseClassName } = this.props
    let value = (<div className={`${baseClassName}-placeholder`}>{this.state.selected.label}
)
    let menu = this.state.isOpen ? <div className={`${baseClassName}-menu`}>{this.buildMenu()}
 : null

    let dropdownClass = classNames({
      [`${baseClassName}-root`]: true,
      'is-open': this.state.isOpen
    })

    return (

        <div className={`${baseClassName}-control`} onMouseDown={this.handleMouseDown.bind(this)} onTouchEnd={this.handleMouseDown.bind(this)}>
          {value}
          <span className={`${baseClassName}-arrow`} />

        {menu}

    )
  }

}

Dropdown.defaultProps = { baseClassName: 'Dropdown' }
export default Dropdown
<commit_msg>fix default flat array lable<commit_after>import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import classNames from 'classnames'

class Dropdown extends Component {
  constructor (props) {
    super(props)
    this.state = {
      selected: props.value || {
        label: props.placeholder || 'Select...',
        value: ''
      },
      isOpen: false
    }
    this.mounted = true
    this.handleDocumentClick = this.handleDocumentClick.bind(this)
    this.fireChangeEvent = this.fireChangeEvent.bind(this)
  }

  componentWillReceiveProps (newProps) {
    if (newProps.value && newProps.value !== this.state.selected) {
      this.setState({selected: newProps.value})
    } else if (newProps.placeholder) {
      this.setState({selected: { label: newProps.placeholder, value: '' }})
    }
  }

  componentDidMount () {
    document.addEventListener('click', this.handleDocumentClick, false)
  }

  componentWillUnmount () {
    this.mounted = false
    document.removeEventListener('click', this.handleDocumentClick, false)
  }

  handleMouseDown (event) {
    if (event.type === 'mousedown' && event.button !== 0) return
    event.stopPropagation()
    event.preventDefault()

    this.setState({
      isOpen: !this.state.isOpen
    })
  }

  setValue (value, label) {
    let newState = {
      selected: {
        value,
        label
      },
      isOpen: false
    }
    this.fireChangeEvent(newState)
    this.setState(newState)
  }

  fireChangeEvent (newState) {
    if (newState.selected !== this.state.selected && this.props.onChange) {
      this.props.onChange(newState.selected)
    }
  }

  renderOption (option) {
    let optionClass = classNames({
      [`${this.props.baseClassName}-option`]: true,
      'is-selected': option === this.state.selected
    })

    let value = option.value || option.label || option
    let label = option.label || option.value || option

    return (

        {label}

    )
  }

  buildMenu () {
    let { options, baseClassName } = this.props
    let ops = options.map((option) => {
      if (option.type === 'group') {
        let groupTitle = (<div className={`${baseClassName}-title`}>{option.name}
)
        let _options = option.items.map((item) => this.renderOption(item))

        return (
          <div className={`${baseClassName}-group`} key={option.name}>
            {groupTitle}
            {_options}

        )
      } else {
        return this.renderOption(option)
      }
    })

    return ops.length ? ops : <div className={`${baseClassName}-noresults`}>No options found

  }

  handleDocumentClick (event) {
    if (this.mounted) {
      if (!ReactDOM.findDOMNode(this).contains(event.target)) {
        this.setState({ isOpen: false })
      }
    }
  }

  render () {
    const { baseClassName } = this.props
    const placeHolderValue = typeof this.state.selected === 'string' ? this.state.selected : this.state.selected.label
    let value = (<div className={`${baseClassName}-placeholder`}>{placeHolderValue}
)
    let menu = this.state.isOpen ? <div className={`${baseClassName}-menu`}>{this.buildMenu()}
 : null

    let dropdownClass = classNames({
      [`${baseClassName}-root`]: true,
      'is-open': this.state.isOpen
    })

    return (

        <div className={`${baseClassName}-control`} onMouseDown={this.handleMouseDown.bind(this)} onTouchEnd={this.handleMouseDown.bind(this)}>
          {value}
          <span className={`${baseClassName}-arrow`} />

        {menu}

    )
  }

}

Dropdown.defaultProps = { baseClassName: 'Dropdown' }
export default Dropdown
<|endoftext|>