<commit_before>var cheerio = require('cheerio');

function FindViewMatches(selectors, content) {
	this.by = {
		binding : function(value) {
			return attrSelector('bind', value);
		},
		model: function(value) {
			return attrSelector('model', value);
		},
		repeater: function(value) {
			return attrSelector('repeat', value);
		},
		css: function(value) {
			return value;
		},
		id: function(value) {
			return prefixSelector('#', value);
		},
		className: function(value) {
			return prefixSelector('.', value);
		},
		name: function(value) {
			return attrSelector('name', value);
		}
	};

	loopSelectors.call(this, selectors, content);
}

function loopSelectors(selectors, content) {
	var self = this;
	var $ = cheerio.load(content);

	selectors.forEach(function(selector) {
		var selectorString = '';
		var value = selector.value;
		var type = selector.type;

		if (!self.by[type] && type !== 'binding') {
			selector.disabled = 1;
			return;
		} else {
			selectorString = self.by[type](value);
		}

		if ($(selectorString).length) {
			selector.found = 1;
		}

		var byBinding = self.by['binding'](value);
		if (type === 'binding' &&
			($(byBinding).length || bindExists(value, content))) {
			selector.found = 1;
		}
	});
}

function attrSelector(attr, value) {
	return [
		'[ng-', attr, '="', value, '"], ',
		'[data-ng-', attr, '="', value, '"]'
	].join('');
}

function prefixSelector(prefix, value) {
	value.replace(new RegExp('^\'+ prefix +'?(.*)$', 'i'), function (match) { return prefix + match });
}

function bindExists(bind, contents) {
	var results;
	var exists = 0;
	var pattern = /{{(.*?)}}/gi;

	while ((results = pattern.exec(contents)) !== null) {
		if (results[1].indexOf(bind) >= 0) {
			exists = 1;
		}
	}

	return exists;
}

module.exports = FindViewMatches;
<commit_msg>Update find-view-matches.js<commit_after>var cheerio = require('cheerio');

function FindViewMatches(selectors, content) {
	this.by = {
		binding : function(value) {
			return attrSelector('bind', value);
		},
		model: function(value) {
			return attrSelector('model', value);
		},
		repeater: function(value) {
			return attrSelector('repeat', value);
		},
		css: function(value) {
			return value;
		},
		id: function(value) {
			return prefixSelector('#', value);
		},
		className: function(value) {
			return prefixSelector('.', value);
		},
		name: function(value) {
			return attrSelector('name', value);
		}
	};

	loopSelectors.call(this, selectors, content);
}

function loopSelectors(selectors, content) {
	var self = this;
	var $ = cheerio.load(content);

	selectors.forEach(function(selector) {
		var selectorString = '';
		var value = selector.value;
		var type = selector.type;

		if (!self.by[type] && type !== 'binding') {
			selector.disabled = 1;
			return;
		} else {
			selectorString = self.by[type](value);
		}

		if ($(selectorString).length) {
			selector.found = 1;
		}

		var byBinding = self.by['binding'](value);
		if (type === 'binding' &&
			($(byBinding).length || bindExists(value, content))) {
			selector.found = 1;
		}
	});
}

function attrSelector(attr, value) {
	return [
		'[ng-', attr, '="', value, '"], ',
		'[data-ng-', attr, '="', value, '"]'
	].join('');
}

function prefixSelector(prefix, value) {
	return value.replace(new RegExp('^\\'+ prefix +'?(.*)$', 'i'), function (match) { return prefix + match });
}

function bindExists(bind, contents) {
	var results;
	var exists = 0;
	var pattern = /{{(.*?)}}/gi;

	while ((results = pattern.exec(contents)) !== null) {
		if (results[1].indexOf(bind) >= 0) {
			exists = 1;
		}
	}

	return exists;
}

module.exports = FindViewMatches;
<|endoftext|>


<commit_before>define([
	'intern!tdd',
	'intern/chai!assert',
	'../async'
], function (test, assert, async) {
	test.suite('core/async', function () {
		test.test('function not called until next turn', function () {
			var dfd = this.async(1000);

			var value = false;

			async(function () {
				value = true;
				async(dfd.callback(function () {
					assert.strictEqual(value, true, 'should be true afterwards');
				}));
			});

			assert.strictEqual(value, false, 'should be false before next turn');
		});
	});
});<commit_msg>Update tests/async to provide more coverage.<commit_after>define([
	'intern!tdd',
	'intern/chai!assert',
	'../async'
], function (test, assert, async) {
	test.suite('core/async', function () {
		test.test('function not called until next turn', function () {
			var dfd = this.async(1000);

			var value = false;

			async(function () {
				value = true;
				async(dfd.callback(function () {
					assert.strictEqual(value, true, 'should be true afterwards');
				}));
			});

			assert.strictEqual(value, false, 'should be false before next turn');
		});
		test.test('preserves scope', function () {
			var dfd = this.async(1000);

			async(dfd.callback(function () {
				assert.equal(this.foo, 'bar', 'this.foo == "bar"');
			}), { foo: 'bar' });
		});
	});
});<|endoftext|>


<commit_before>var ftdi = require("ftdi"),
    DeviceLoader = require('../deviceloader'),
    util = require('util'),
    _ = require('lodash');

/**
 * A ftdideviceloader can check if there are available some serial devices.
 * @param {Object} Device The constructor function of the device.
 */
function FtdiDeviceLoader(Device, vendorId, productId) {

  // call super class
  DeviceLoader.call(this);

  this.Device = Device;

  this.vendorId = vendorId;
  this.productId = productId;
}

util.inherits(FtdiDeviceLoader, DeviceLoader);

/**
 * Calls the callback with an array of devices.
 * @param  {Function} callback The function, that will be called when finished lookup.
 *                             `function(err, devices){}` devices is an array of Device objects.
 */
FtdiDeviceLoader.prototype.lookup = function(callback) {
  var self = this;
  ftdi.find(this.vendorId, this.productId, function(err, ports) {
    if (err) { return callback(err); }

    var resPorts = ports;
    if (self.filter) {
      resPorts = self.filter(ports);
    }

    var devices = _.map(resPorts, function(p) {
      var found = _.find(self.oldDevices, function(dev) {
        return dev.get('locationId') === p.locationId &&
               dev.get('vendorId') === p.vendorId &&
               dev.get('productId') === p.productId &&
               dev.get('serial') === p.serial;
      });
      if (found) {
        return found;
      } else {
        return new self.Device(p);
      }
    }) || [];

    callback(null, devices);
  });
};

module.exports = FtdiDeviceLoader;<commit_msg>rename serial to serialNumber<commit_after>var ftdi = require("ftdi"),
    DeviceLoader = require('../deviceloader'),
    util = require('util'),
    _ = require('lodash');

/**
 * A ftdideviceloader can check if there are available some serial devices.
 * @param {Object} Device The constructor function of the device.
 */
function FtdiDeviceLoader(Device, vendorId, productId) {

  // call super class
  DeviceLoader.call(this);

  this.Device = Device;

  this.vendorId = vendorId;
  this.productId = productId;
}

util.inherits(FtdiDeviceLoader, DeviceLoader);

/**
 * Calls the callback with an array of devices.
 * @param  {Function} callback The function, that will be called when finished lookup.
 *                             `function(err, devices){}` devices is an array of Device objects.
 */
FtdiDeviceLoader.prototype.lookup = function(callback) {
  var self = this;
  ftdi.find(this.vendorId, this.productId, function(err, ports) {
    if (err) { return callback(err); }

    var resPorts = ports;
    if (self.filter) {
      resPorts = self.filter(ports);
    }

    var devices = _.map(resPorts, function(p) {
      var found = _.find(self.oldDevices, function(dev) {
        return dev.get('locationId') === p.locationId &&
               dev.get('vendorId') === p.vendorId &&
               dev.get('productId') === p.productId &&
               dev.get('serialNumber') === p.serialNumber;
      });
      if (found) {
        return found;
      } else {
        return new self.Device(p);
      }
    }) || [];

    callback(null, devices);
  });
};

module.exports = FtdiDeviceLoader;<|endoftext|>


<commit_before>/**
 * Reencode audio & video without creating files first
 *
 * Requirements: ffmpeg, ether via a manual installation or via ffmpeg-static
 */

// Buildin with nodejs
const fs = require('fs');
const cp = require('child_process');
const readline = require('readline');
// External modules
const ytdl = require('../');
const ffmpeg = require('ffmpeg-static');
// Global constants
const ref = 'https://www.youtube.com/watch?v=aqz-KE-bpKQ';
const tracker = {
  start: Date.now(),
  audio: { downloaded: 0, total: Infinity },
  video: { downloaded: 0, total: Infinity },
  merged: { frame: 0, speed: '0x', fps: 0 },
};

// Get audio and video stream going
const audio = ytdl(ref, { filter: 'audioonly', quality: 'highestaudio' })
  .on('progress', (_, downloaded, total) => {
    tracker.audio = { downloaded, total };
  });
const video = ytdl(ref, { filter: 'videoonly', quality: 'highestvideo' })
  .on('progress', (_, downloaded, total) => {
    tracker.video = { downloaded, total };
  });

// Get the progress bar going
const progressbar = setInterval(() => {
  readline.cursorTo(process.stdout, 0);
  const toMB = i => (i / 1024 / 1024).toFixed(2);

  process.stdout.write(`Audio  | ${(tracker.audio.downloaded / tracker.audio.total * 100).toFixed(2)}% processed `);
  process.stdout.write(`(${toMB(tracker.audio.downloaded)}MB of ${toMB(tracker.audio.total)}MB).${' '.repeat(10)}\n`);

  process.stdout.write(`Video  | ${(tracker.video.downloaded / tracker.video.total * 100).toFixed(2)}% processed `);
  process.stdout.write(`(${toMB(tracker.video.downloaded)}MB of ${toMB(tracker.video.total)}MB).${' '.repeat(10)}\n`);

  process.stdout.write(`Merged | processing frame ${tracker.merged.frame} `);
  process.stdout.write(`(at ${tracker.merged.fps} fps => ${tracker.merged.speed}).${' '.repeat(10)}\n`);

  process.stdout.write(`running for: ${((Date.now() - tracker.start) / 1000 / 60).toFixed(2)} Minutes.`);
  readline.moveCursor(process.stdout, 0, -3);
}, 1000);

// Start the ffmpeg child process
const ffmpegProcess = cp.spawn(ffmpeg, [
  // Remove ffmpeg's console spamming
  '-loglevel', '0', '-hide_banner',
  // Redirect/enable progress messages
  '-progress', 'pipe:3',
  // 3 second audio offset
  '-itsoffset', '3.0', '-i', 'pipe:4',
  '-i', 'pipe:5',
  // Rescale the video
  '-vf', 'scale=320:240',
  // Choose some fancy codes
  '-c:v', 'libx265', '-x265-params', 'log-level=0',
  '-c:a', 'flac',
  // Define output container
  '-f', 'matroska', 'pipe:6',
], {
  windowsHide: true,
  stdio: [
    /* Standard: stdin, stdout, stderr */
    'inherit', 'inherit', 'inherit',
    /* Custom: pipe:3, pipe:4, pipe:5, pipe:6 */
    'pipe', 'pipe', 'pipe', 'pipe',
  ],
});
ffmpegProcess.on('close', () => {
  process.stdout.write('\n\n\n\n');
  clearInterval(progressbar);
  console.log('done');
});

// Link streams
// FFmpeg creates the transformer streams and we just have to insert / read data
ffmpegProcess.stdio[3].on('data', chunk => {
  // Parse the param=value list returned by ffmpeg
  const lines = chunk.toString().trim().split('\n');
  const args = {};
  for (const l of lines) {
    const [key, value] = l.trim().split('=');
    args[key] = value;
  }
  tracker.merged = args;
});
audio.pipe(ffmpegProcess.stdio[4]);
video.pipe(ffmpegProcess.stdio[5]);
ffmpegProcess.stdio[6].pipe(fs.createWriteStream('./out.mkv'));
<commit_msg>docs(example): update ffmpeg example (#818)<commit_after>/**
 * Reencode audio & video without creating files first
 *
 * Requirements: ffmpeg, ether via a manual installation or via ffmpeg-static
 *
 * If you need more complex features like an output-stream you can check the older, more complex example:
 * https://github.com/fent/node-ytdl-core/blob/cc6720f9387088d6253acc71c8a49000544d4d2a/example/ffmpeg.js
 */

// Buildin with nodejs
const cp = require('child_process');
const readline = require('readline');
// External modules
const ytdl = require('../');
const ffmpeg = require('ffmpeg-static');
// Global constants
const ref = 'https://www.youtube.com/watch?v=aqz-KE-bpKQ';
const tracker = {
  start: Date.now(),
  audio: { downloaded: 0, total: Infinity },
  video: { downloaded: 0, total: Infinity },
  merged: { frame: 0, speed: '0x', fps: 0 },
};

// Get audio and video streams
const audio = ytdl(ref, { quality: 'highestaudio' })
  .on('progress', (_, downloaded, total) => {
    tracker.audio = { downloaded, total };
  });
const video = ytdl(ref, { quality: 'highestvideo' })
  .on('progress', (_, downloaded, total) => {
    tracker.video = { downloaded, total };
  });

// Prepare the progress bar
let progressbarHandle = null;
const progressbarInterval = 1000;
const showProgress = () => {
  readline.cursorTo(process.stdout, 0);
  const toMB = i => (i / 1024 / 1024).toFixed(2);

  process.stdout.write(`Audio  | ${(tracker.audio.downloaded / tracker.audio.total * 100).toFixed(2)}% processed `);
  process.stdout.write(`(${toMB(tracker.audio.downloaded)}MB of ${toMB(tracker.audio.total)}MB).${' '.repeat(10)}\n`);

  process.stdout.write(`Video  | ${(tracker.video.downloaded / tracker.video.total * 100).toFixed(2)}% processed `);
  process.stdout.write(`(${toMB(tracker.video.downloaded)}MB of ${toMB(tracker.video.total)}MB).${' '.repeat(10)}\n`);

  process.stdout.write(`Merged | processing frame ${tracker.merged.frame} `);
  process.stdout.write(`(at ${tracker.merged.fps} fps => ${tracker.merged.speed}).${' '.repeat(10)}\n`);

  process.stdout.write(`running for: ${((Date.now() - tracker.start) / 1000 / 60).toFixed(2)} Minutes.`);
  readline.moveCursor(process.stdout, 0, -3);
};

// Start the ffmpeg child process
const ffmpegProcess = cp.spawn(ffmpeg, [
  // Remove ffmpeg's console spamming
  '-loglevel', '8', '-hide_banner',
  // Redirect/Enable progress messages
  '-progress', 'pipe:3',
  // Set inputs
  '-i', 'pipe:4',
  '-i', 'pipe:5',
  // Map audio & video from streams
  '-map', '0:a',
  '-map', '1:v',
  // Keep encoding
  '-c:v', 'copy',
  // Define output file
  'out.mkv',
], {
  windowsHide: true,
  stdio: [
    /* Standard: stdin, stdout, stderr */
    'inherit', 'inherit', 'inherit',
    /* Custom: pipe:3, pipe:4, pipe:5 */
    'pipe', 'pipe', 'pipe',
  ],
});
ffmpegProcess.on('close', () => {
  console.log('done');
  // Cleanup
  process.stdout.write('\n\n\n\n');
  clearInterval(progressbarHandle);
  // Close the programm
  process.exit(0);
});

// Link streams
// FFmpeg creates the transformer streams and we just have to insert / read data
ffmpegProcess.stdio[3].on('data', chunk => {
  // Start the progress bar
  if (!progressbarHandle) progressbarHandle = setInterval(showProgress, progressbarInterval);
  // Parse the param=value list returned by ffmpeg
  const lines = chunk.toString().trim().split('\n');
  const args = {};
  for (const l of lines) {
    const [key, value] = l.split('=');
    args[key.trim()] = value.trim();
  }
  tracker.merged = args;
});
audio.pipe(ffmpegProcess.stdio[4]);
video.pipe(ffmpegProcess.stdio[5]);
<|endoftext|>


<commit_before>var TestCase = require('../src/scenario/webdriver');
var wd = require('wd');
var chai = require('chai');
var assert = chai.assert;

var Scenario = TestCase.extend({
    name: 'google',
    doRun: function (browser) {
        browser
            .get('http://google.fr')
            .title(function (title) {
                assert.equal(title, 'Google');
/*            })
            ._title()
            ._title(function (err, title, done) {
                console.log('Raw');
                done();
            })
            .title(function (title) {
                console.log('after');
*/            });
    }
});

module.exports = new Scenario();
<commit_msg>Add a screenshot command to google example.<commit_after>var TestCase = require('../src/scenario/webdriver');
var wd = require('wd');
var chai = require('chai');
var assert = chai.assert;
var fs = require('fs');

var Scenario = TestCase.extend({
    name: 'google',
    doRun: function (browser, remote, desired) {
        browser
            .get('http://google.fr')
            .title(function (title) {
                assert.equal(title, 'Google');
            })
            .setWindowSize(1024, 780)
            ._takeScreenshot(function (err, image, next) {
                if (err) {throw err;}
                saveScreenshot('homepage', image, desired, next);
/*          })
            ._title()
            ._title(function (err, title, done) {
                console.log('Raw');
                done();
            })
            .title(function (title) {
                console.log('after');
*/            });
    }
});

function saveScreenshot(id, b64screen, desired, next) {
    var file = filename(id, desired);
    fs.writeFile(file, b64screen, 'base64', function (err) {
        next(err);
    });
}

function filename(id, desired) {
    return [__dirname, 'screenshots', id+'_'+desired.browserName+'.png'].join('/');
}

module.exports = new Scenario();
<|endoftext|>


<commit_before>'use strict';

const async = require('async');
const helpers = require('../helpers');
const storage = require('../services/storage');
const messenger = require('../services/messenger');
const twitter = require('../services/twitter');

function onName(sender, result) {
  let client = storage.get('clients').get(sender);
  let name = result.parameters['given-name'];

  client.delete('lastQuestion');

  if (client.has('name')) {
    messenger.sendMessage(sender, {
      text: `Alright, I will now call you ${name}!`
    });
  } else {
    messenger.sendMessage(sender, {
      text: `Thanks ${name}, you can always change how I should call you by typing something like "Call me John" :)`
    }, () => {
      if (!client.has('firstHelp')) {
        onHelp(sender, result);
        client.set('firstHelp', true);
      }
    });
  }

  client.set('name', name);
}

function onReset(sender, result) {
  storage.get('clients').delete(sender);

  messenger.sendMessage(sender, {
    text: 'Done. You can now start over.'
  });
}

function onTrending(sender, result) {
  let client = storage.get('clients').get(sender);

  if (!client.has('location')) {
    client.set('wasAsking', 'trending');
    return onLocation(sender);
  }

  client.delete('lastQuestion');
  client.set('wasAsking', null);

  messenger.sendMessage(sender, {
    text: 'Let me check trending topics on Twitter for you.'
  }, () => {
    messenger.sendAction(sender, 'typing_on', () => {
      let location = client.get('location');
      let woeid = 1;
      let name = 'Worldwide';

      if (!location.none) {
        return twitter.get('trends/closest', location.coordinates, (err, data, response) => {
          if (data && data[0] && data[0].woeid) {
            woeid = data[0].woeid;
            name = data[0].name;
          }

          onTrendingForWoeid(sender, {}, woeid, name);
        });
      }

      onTrendingForWoeid(sender, {}, woeid, name);
    });
  });
}

function onTweet(sender, result, more) {
  let topic = result.parameters.topic;
  let client = storage.get('clients').get(sender);
  let name = client.get('name');

  client.delete('lastQuestion');
  client.set('topic', topic);

  twitter.get('search/tweets', {
    q: topic,
    count: 5
  }, function(err, data, response) {
    if (err || !data || !data.statuses) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error communicating with Twitter.'
      });
    }

    let messages = data.statuses.slice(0, 5).map((tweet) => {
      return tweet.text;
    });

    if (messages.length < 1) {
      return messenger.sendMessage(sender, {
        text: 'Whoops, looks like I didn\'t get any Tweets back from Twitter. Sorry :('
      });
    }

    let cond = 'more';
    if (!more) {
      cond = 'a few';
      messages.push('Tell me if you want to see more :)');
    }

    messenger.sendMessage(sender, {
      text: name ? `Here are ${cond} Tweets, ${name}:` : `Here are ${cond} tweets:`
    }, () => {
      async.eachSeries(messages, (textPart, callback) => {
        messenger.sendMessage(sender, {
          text: textPart
        }, callback);
      });
    });
  });
}

function onOkay(sender, result) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  let response = helpers.randomItem([
    "✌",
    "👌",
    "👍",
    "🎉",
    "😊",
    "😉"
  ]);

  messenger.sendMessage(sender, {
    text: response
  });
}

function onMore(sender, result) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  if (!storage.get('clients').get(sender).has('topic')) {
    return messenger.sendMessage(sender, {
      text: 'Please tell me, which topic I should get you tweets for or ask me for trending topics.'
    })
  }

  onTweet(sender, {
    parameters: {
      topic: storage.get('clients').get(sender).get('topic')
    }
  }, true);
}

function onTrendingForWoeid(sender, result, woeid, locationName) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  twitter.get('trends/place', {
    id: woeid
  }, (err, data, response) => {
    if (err) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error speaking to Twitter.'
      });
    }

    if (!data || !data[0] || !data[0].trends || !data[0].trends.length) {
      client.set('lastQuestion', 'change_location');

      return async.eachSeries([
        `There are currently no trends available for ${locationName} :(`,
        'Do you want to change your location?'
      ], (textPart, callback) => {
        messenger.sendMessage(sender, {
          text: textPart
        }, callback);
      });
    }

    let quickReplies = data[0].trends.slice(0, 11).map((val) => {
      return {
        'content_type': 'text',
        'title': val.name,
        'payload': `TWITTER_TOPIC`
      };
    });

    messenger.sendMessage(sender, {
      text: `Here are some trending topics for ${locationName}. You can tap a tag and I will get you a few tweets for it :)`,
      "quick_replies": quickReplies
    });
  });
}

function onLocation(sender, result, coordinates, title) {
  let client = storage.get('clients').get(sender);
  let hadLocation = false;

  if (!coordinates) {
    client.set('lastQuestion', 'location');

    return messenger.sendMessage(sender, {
      text: 'Please click the button below to share your location:',
      'quick_replies': [{
        'content_type': 'location'
      }, {
        'content_type': 'text',
        'title': 'No thanks.',
        'payload': 'No'
      }]
    });
  }

  if (client.has('location')) {
    hadLocation = true;
  }

  twitter.get('trends/closest', coordinates, (err, data, response) => {
    if (err) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error talking to Twitter :('
      });
    }

    let woeid = 1;
    let name = 'Worldwide';

    if (data && data[0] && data[0].woeid) {
      woeid = data[0].woeid;
      name = data[0].name;
    } else {
      return messenger.sendMessage(sender, {
        text: 'Sorry, I couldn\'t associate your location :('
      });
    }

    client.set('location', {
      none: false,
      name: name,
      coordinates: coordinates
    });

    messenger.sendMessage(sender, {
      text: hadLocation && name ?
        `I've changed your location to ${name}.` : 'Thanks for sharing your location :)'
    }, () => {
      if (client.get('wasAsking') === 'trending') {
        return onTrending(sender);
      }

      client.set('wasAsking', null);
      client.set('lastQuestion', 'trending');

      return messenger.sendMessage(sender, {
        text: 'Do you want to see trending topics around?',
        'quick_replies': [{
          'content_type': 'text',
          'title': 'Yes, please.',
          'payload': 'Yes'
        }, {
          'content_type': 'text',
          'title': 'Nope.',
          'payload': 'No'
        }]
      });
    });
  });
}

function onYes(sender, result) {
  const client = storage.get('clients').get(sender);
  let lastQuestion = client.get('lastQuestion');
  let wasAsking = client.get('wasAsking');
  let interacted = false;

  client.delete('lastQuestion');
  client.delete('wasAsking');

  if (lastQuestion === 'name' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Cool, can you please type your name?`
    });
  }

  if (lastQuestion === 'location' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `OK, please tap the button to share your location. Alternatively you can also type it in here.`
    });
  }

  if (lastQuestion === 'help' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onHelp(sender, result);
    });
  }

  if (lastQuestion === 'change_location' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onLocation(sender);
    });
  }

  if (lastQuestion === 'trending' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onTrending(sender);
    });
  }

  if (wasAsking === 'trending' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onTrending(sender);
    });
  }

  if (!interacted) {
    setTimeout(() => {
      onDefault(sender, result);
    })
  }
}

function onNo(sender, result) {
  const client = storage.get('clients').get(sender);
  let lastQuestion = client.get('lastQuestion');
  let wasAsking = client.get('wasAsking');
  let interacted = false;

  client.delete('lastQuestion');
  client.delete('wasAsking');

  if (lastQuestion === 'name' && !interacted) {
    interacted = true;
    async.eachSeries([
      `That's fine too :)`,
      'Anyway, if you change your mind, just say something like "Call me John".'
    ], (textPart, callback) => {
      messenger.sendMessage(sender, {
        text: textPart
      }, callback);
    }, () => {
      if (!client.has('firstHelp')) {
        onHelp(sender, result);
        client.set('firstHelp', true);
      }
    });
  }

  if (lastQuestion === 'help' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Don't worry, let me know if you need help.`
    });
  }

  if (lastQuestion === 'location' && !interacted) {
    interacted = true;

    client.set('location', {
      none: true
    });

    messenger.sendMessage(sender, {
      text: `OK, let me know if you change your mind.`
    });
  }

  if (lastQuestion === 'change_location' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: 'Alright, maybe just try again later.'
    });
  }

  if (lastQuestion === 'trending' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Okay :)`
    });
  }

  if (!interacted) {
    setTimeout(() => {
      onDefault(sender, result);
    });
  }
}

function onHelp(sender, result) {
  const client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  messenger.sendMessage(sender, {
    text: 'You can do things like:',
    'quick_replies': [{
      'content_type': 'text',
      'title': 'What\'s trending?',
      'payload': 'What\'s trending?'
    }, {
      'content_type': 'text',
      'title': 'Tweets for #hashtag',
      'payload': 'Tweets for #hashtag'
    }, {
      'content_type': 'text',
      'title': 'Share my location',
      'payload': 'Share my location'
    }, {
      'content_type': 'text',
      'title': 'Call me John',
      'payload': 'Call me John'
    }, {
      'content_type': 'text',
      'title': 'I need help',
      'payload': 'I need help'
    }, {
      'content_type': 'text',
      'title': 'Reset',
      'payload': 'Reset'
    }]
  });
}

function onDefault(sender, result) {
  const client = storage.get('clients').get(sender);
  client.set('lastQuestion', 'help');

  let response = helpers.randomItem([
    "I'm sorry. I'm having trouble understanding the question. Do you need help?",
    "I think I may have misunderstood your last statement.  Do you need help?",
    "I'm sorry. I didn't quite grasp what you just said.  Do you need help?",
    "I'm a bit confused by that last part.  Do you need help?",
    "I'm not totally sure about that.  Do you need help?",
    "I'm not sure I follow. Do you need help?",
    "I'm afraid I don't understand. Do you need help?",
    "I'm a bit confused. Do you need help?"
  ]);

  messenger.sendMessage(sender, {
    text: response
  });
}

function onFirst(sender, result) {
  const client = storage.get('clients').get(sender);
  client.set('lastQuestion', 'name');

  messenger.sendMessage(sender, {
    text: 'Hi there! This is the first time writing with me. Would you tell me your name?'
  });
}

module.exports = {
  onName,
  onReset,
  onTrending,
  onLocation,
  onYes,
  onNo,
  onDefault,
  onFirst,
  onTweet,
  onMore,
  onHelp,
  onOkay
};
<commit_msg>Improvements.<commit_after>'use strict';

const async = require('async');
const helpers = require('../helpers');
const storage = require('../services/storage');
const messenger = require('../services/messenger');
const twitter = require('../services/twitter');

function onName(sender, result) {
  let client = storage.get('clients').get(sender);
  let name = result.parameters['given-name'];

  client.delete('lastQuestion');

  if (client.has('name')) {
    messenger.sendMessage(sender, {
      text: `Alright, I will now call you ${name}!`
    });
  } else {
    messenger.sendMessage(sender, {
      text: `Thanks ${name}, you can always change how I should call you by typing something like "Call me John" :)`
    }, () => {
      if (!client.has('firstHelp')) {
        onHelp(sender, result);
        client.set('firstHelp', true);
      }
    });
  }

  client.set('name', name);
}

function onReset(sender, result) {
  storage.get('clients').delete(sender);

  messenger.sendMessage(sender, {
    text: 'Done. You can now start over.'
  });
}

function onTrending(sender, result) {
  let client = storage.get('clients').get(sender);

  if (!client.has('location')) {
    client.set('wasAsking', 'trending');
    return onLocation(sender);
  }

  client.delete('lastQuestion');
  client.set('wasAsking', null);

  messenger.sendMessage(sender, {
    text: 'Let me check trending topics on Twitter for you.'
  }, () => {
    messenger.sendAction(sender, 'typing_on', () => {
      let location = client.get('location');
      let woeid = 1;
      let name = 'Worldwide';

      if (!location.none) {
        return twitter.get('trends/closest', location.coordinates, (err, data, response) => {
          if (data && data[0] && data[0].woeid) {
            woeid = data[0].woeid;
            name = data[0].name;
          }

          onTrendingForWoeid(sender, {}, woeid, name);
        });
      }

      onTrendingForWoeid(sender, {}, woeid, name);
    });
  });
}

function onTweet(sender, result, more) {
  let topic = result.parameters.topic;
  let client = storage.get('clients').get(sender);
  let name = client.get('name');

  client.delete('lastQuestion');
  client.set('topic', topic);

  twitter.get('search/tweets', {
    q: topic,
    count: 5
  }, function(err, data, response) {
    if (err || !data || !data.statuses) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error communicating with Twitter.'
      });
    }

    let messages = data.statuses.slice(0, 5).map((tweet) => {
      return tweet.text;
    });

    if (messages.length < 1) {
      return messenger.sendMessage(sender, {
        text: 'Whoops, looks like I didn\'t get any Tweets back from Twitter. Sorry :('
      });
    }

    let cond = 'more';
    if (!more) {
      cond = 'a few';
      messages.push('Tell me if you want to see more :)');
    }

    messenger.sendMessage(sender, {
      text: name ? `Here are ${cond} Tweets, ${name}:` : `Here are ${cond} tweets:`
    }, () => {
      async.eachSeries(messages, (textPart, callback) => {
        messenger.sendMessage(sender, {
          text: textPart
        }, callback);
      });
    });
  });
}

function onOkay(sender, result) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  let response = helpers.randomItem([
    "✌",
    "👌",
    "👍",
    "🎉",
    "😊",
    "😉"
  ]);

  messenger.sendMessage(sender, {
    text: response
  });
}

function onMore(sender, result) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  if (!storage.get('clients').get(sender).has('topic')) {
    return messenger.sendMessage(sender, {
      text: 'Please tell me, which topic I should get you tweets for or ask me for trending topics.'
    })
  }

  onTweet(sender, {
    parameters: {
      topic: storage.get('clients').get(sender).get('topic')
    }
  }, true);
}

function onTrendingForWoeid(sender, result, woeid, locationName) {
  let client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  twitter.get('trends/place', {
    id: woeid
  }, (err, data, response) => {
    if (err) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error speaking to Twitter.'
      });
    }

    if (!data || !data[0] || !data[0].trends || !data[0].trends.length) {
      client.set('lastQuestion', 'change_location');

      return async.eachSeries([
        `There are currently no trends available for ${locationName} :(`,
        'Do you want to change your location?'
      ], (textPart, callback) => {
        messenger.sendMessage(sender, {
          text: textPart
        }, callback);
      });
    }

    let quickReplies = data[0].trends.slice(0, 11).map((val) => {
      return {
        'content_type': 'text',
        'title': val.name,
        'payload': `TWITTER_TOPIC`
      };
    });

    messenger.sendMessage(sender, {
      text: `Here are some trending topics for ${locationName}. You can tap a tag and I will get you a few tweets for it :)`,
      "quick_replies": quickReplies
    });
  });
}

function onLocation(sender, result, coordinates, title) {
  let client = storage.get('clients').get(sender);
  let hadLocation = false;

  if (!coordinates) {
    client.set('lastQuestion', 'location');

    return messenger.sendMessage(sender, {
      text: 'Please click the button below to share your location:',
      'quick_replies': [{
        'content_type': 'location'
      }, {
        'content_type': 'text',
        'title': 'No thanks.',
        'payload': 'No'
      }]
    });
  }

  if (client.has('location')) {
    hadLocation = true;
  }

  twitter.get('trends/closest', coordinates, (err, data, response) => {
    if (err) {
      return messenger.sendMessage(sender, {
        text: 'Sorry, there was an error talking to Twitter :('
      });
    }

    let woeid = 1;
    let name = 'Worldwide';

    if (data && data[0] && data[0].woeid) {
      woeid = data[0].woeid;
      name = data[0].name;
    } else {
      return messenger.sendMessage(sender, {
        text: 'Sorry, I couldn\'t associate your location :('
      });
    }

    client.set('location', {
      none: false,
      name: name,
      coordinates: coordinates
    });

    messenger.sendMessage(sender, {
      text: hadLocation && name ?
        `I've changed your location to ${name}.` : 'Thanks for sharing your location :)'
    }, () => {
      if (client.get('wasAsking') === 'trending') {
        return onTrending(sender);
      }

      client.set('wasAsking', null);
      client.set('lastQuestion', 'trending');

      return messenger.sendMessage(sender, {
        text: 'Do you want to see trending topics around?',
        'quick_replies': [{
          'content_type': 'text',
          'title': 'Yes, please.',
          'payload': 'Yes'
        }, {
          'content_type': 'text',
          'title': 'Nope.',
          'payload': 'No'
        }]
      });
    });
  });
}

function onYes(sender, result) {
  const client = storage.get('clients').get(sender);
  let lastQuestion = client.get('lastQuestion');
  let wasAsking = client.get('wasAsking');
  let interacted = false;

  client.delete('lastQuestion');
  client.delete('wasAsking');

  if (lastQuestion === 'name' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Cool, can you please type your name?`
    });
  }

  if (lastQuestion === 'location' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `OK, please tap the button to share your location. Alternatively you can also type it in here.`
    });
  }

  if (lastQuestion === 'help' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onHelp(sender, result);
    });
  }

  if (lastQuestion === 'change_location' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onLocation(sender);
    });
  }

  if (lastQuestion === 'trending' && !interacted) {
    interacted = true;

    setTimeout(() => {
      onTrending(sender);
    });
  }

  if (wasAsking === 'trending') {
    interacted = true;

    setTimeout(() => {
      onTrending(sender);
    });
  }

  if (!interacted) {
    setTimeout(() => {
      onDefault(sender, result);
    })
  }
}

function onNo(sender, result) {
  const client = storage.get('clients').get(sender);
  let lastQuestion = client.get('lastQuestion');
  let wasAsking = client.get('wasAsking');
  let interacted = false;

  client.delete('lastQuestion');
  client.delete('wasAsking');

  if (lastQuestion === 'name' && !interacted) {
    interacted = true;
    async.eachSeries([
      `That's fine too :)`,
      'Anyway, if you change your mind, just say something like "Call me John".'
    ], (textPart, callback) => {
      messenger.sendMessage(sender, {
        text: textPart
      }, callback);
    }, () => {
      if (!client.has('firstHelp')) {
        onHelp(sender, result);
        client.set('firstHelp', true);
      }
    });
  }

  if (lastQuestion === 'help' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Don't worry, let me know if you need help.`
    });
  }

  if (lastQuestion === 'location' && !interacted) {
    interacted = true;

    client.set('location', {
      none: true
    });

    messenger.sendMessage(sender, {
      text: `OK, let me know if you change your mind.`
    });
  }

  if (lastQuestion === 'change_location' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: 'Alright, maybe just try again later.'
    });
  }

  if (lastQuestion === 'trending' && !interacted) {
    interacted = true;

    messenger.sendMessage(sender, {
      text: `Okay :)`
    });
  }

  if (wasAsking === 'trending') {
    interacted = true;

    setTimeout(() => {
      onTrending(sender);
    });
  }

  if (!interacted) {
    setTimeout(() => {
      onDefault(sender, result);
    });
  }
}

function onHelp(sender, result) {
  const client = storage.get('clients').get(sender);
  client.delete('lastQuestion');

  messenger.sendMessage(sender, {
    text: 'You can do things like:',
    'quick_replies': [{
      'content_type': 'text',
      'title': 'What\'s trending?',
      'payload': 'What\'s trending?'
    }, {
      'content_type': 'text',
      'title': 'Tweets for #hashtag',
      'payload': 'Tweets for #hashtag'
    }, {
      'content_type': 'text',
      'title': 'Share my location',
      'payload': 'Share my location'
    }, {
      'content_type': 'text',
      'title': 'Call me John',
      'payload': 'Call me John'
    }, {
      'content_type': 'text',
      'title': 'I need help',
      'payload': 'I need help'
    }, {
      'content_type': 'text',
      'title': 'Reset',
      'payload': 'Reset'
    }]
  });
}

function onDefault(sender, result) {
  const client = storage.get('clients').get(sender);
  client.set('lastQuestion', 'help');

  let response = helpers.randomItem([
    "I'm sorry. I'm having trouble understanding the question. Do you need help?",
    "I think I may have misunderstood your last statement.  Do you need help?",
    "I'm sorry. I didn't quite grasp what you just said.  Do you need help?",
    "I'm a bit confused by that last part.  Do you need help?",
    "I'm not totally sure about that.  Do you need help?",
    "I'm not sure I follow. Do you need help?",
    "I'm afraid I don't understand. Do you need help?",
    "I'm a bit confused. Do you need help?"
  ]);

  messenger.sendMessage(sender, {
    text: response
  });
}

function onFirst(sender, result) {
  const client = storage.get('clients').get(sender);
  client.set('lastQuestion', 'name');

  messenger.sendMessage(sender, {
    text: 'Hi there! This is the first time writing with me. Would you tell me your name?'
  });
}

module.exports = {
  onName,
  onReset,
  onTrending,
  onLocation,
  onYes,
  onNo,
  onDefault,
  onFirst,
  onTweet,
  onMore,
  onHelp,
  onOkay
};
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var test = require('tape').test;

var mddata = require('../src/index');

var basePath = path.resolve('tests/data');

var checkFileTransformation = function (t, fileName) {
	var expectedFile = path.join(basePath, fileName + '.js');
	var expected = JSON.parse(fs.readFileSync(expectedFile, 'utf-8'));

	var sourceFile = path.join(basePath, fileName + '.md');
	var result = mddata(sourceFile);
	t.equals(expected, result, 'result after data extraction matches expected file content');
	t.end();
};

test('data-extraction', function(t) {
	checkFileTransformation(t, 'data');
});
<commit_msg>fixed typo in test case<commit_after>var path = require('path');
var fs = require('fs');
var test = require('tape').test;

var mddata = require('../src/index');

var basePath = path.resolve('tests/data');

var checkFileTransformation = function (t, fileName) {
	var expectedFile = path.join(basePath, fileName + '.json');
	var expected = JSON.parse(fs.readFileSync(expectedFile, 'utf-8'));

	var sourceFile = path.join(basePath, fileName + '.md');
	var result = mddata(sourceFile);
	t.equals(expected, result, 'result after data extraction matches expected file content');
	t.end();
};

test('data-extraction', function(t) {
	checkFileTransformation(t, 'data');
});
<|endoftext|>


<commit_before>var
  $ = require('jquery'),
  _ = require('underscore'),
  app = require('../os-import/app'),
  assert = require('chai').assert,
  Browser = require('zombie');

var
  browser,

  SUBMIT_LABELS = {
    default: 'Waiting for files'
  };

process.env.NODE_ENV = 'test';
Browser.localhost('127.0.0.1', process.env.PORT || 3000);
browser = new Browser({maxWait: 30000, silent: true});

before(function(done) {
  // Run the server
  app.listen(3000, function() { done(); });
});

describe('Core', function() {
  // Ensure we have time for request to reoslve, etc.
  this.timeout(25000);

  it('is alive', function(done) {
    browser.visit('/', function() {
      browser.assert.success();
      done();
    });
  });

  it('reserves / for landing page only and does not load app scripts', function(done) {
    browser.visit('/some/non/index/route', function() {
      browser.assert.element('script[src="app.min.js"]');
      browser.assert.element('script[src="vendor.min.js"]');

      browser.visit('/', function() {
        browser.assert.elements('script[src="app.min.js"]', {exactly: 0});
        browser.assert.elements('script[src="vendor.min.js"]', {exactly: 0});
        done();
      });
    });
  });
});

describe('Form for creating data', function() {
  this.timeout(25000);

  beforeEach(function(done) {
    browser.visit('/create', function() { done(); });
  });

  it('is alive', function(done) {
    browser.assert.success();
    done();
  });

  it('updates URL instantly with slugged name when it changes', function(done) {
    browser.fill('[data-editors=name] input[name=name]', 'This is the name');
    browser.fire('[data-editors=name] input[name=name]', 'keyup');
    browser.assert.text('[data-editors=name] [data-id=slug]', 'this-is-the-name');
    done();
  });

  it('has disabled submit button with default label reading "' + SUBMIT_LABELS.default + '"', function(done) {
    browser.assert.text('[data-id="submit-message"]', SUBMIT_LABELS.default);
    browser.assert.hasClass('[data-id="submit"]', 'form-button--disabled');
    done();
  });

  it('switches submit button into loading state when uploading a file/URL', function(done) {
    assert(false);
  });

  it('uploads and validates valid CSV from local file', function(done) {
    assert(false);
  });

  it('uploads and validates malformed CSV from local file', function(done) {
    assert(false);
  });

  it('uploads and validates valid CSV from URL', function(done) {
    assert(false);
  });

  it('uploads and validates malformed CSV from URL', function(done) {
    assert(false);
  });

  it('allows passing to the next step when there is file and it is valid', function(done) {
    assert(false);
  });

  it('disallows passing to the next step when there is no file or it is invalid', function(done) {
    assert(false);
  });
});
<commit_msg>Loading state test passed (#11)<commit_after>var
  $ = require('jquery'),
  _ = require('underscore'),
  app = require('../os-import/app'),
  assert = require('chai').assert,
  Browser = require('zombie'),
  path = require('path');

var
  browser,
  dataDir = path.join('.', 'tests', 'data');

  submitLabels = {
    default: 'Waiting for files'
  };

process.env.NODE_ENV = 'test';
Browser.localhost('127.0.0.1', process.env.PORT || 3000);
browser = new Browser({maxWait: 30000, silent: true});

before(function(done) {
  // Run the server
  app.listen(3000, function() { done(); });
});

describe('Core', function() {
  // Ensure we have time for request to reoslve, etc.
  this.timeout(25000);

  it('is alive', function(done) {
    browser.visit('/', function() {
      browser.assert.success();
      done();
    });
  });

  it('reserves / for landing page only and does not load app scripts', function(done) {
    browser.visit('/some/non/index/route', function() {
      browser.assert.element('script[src="app.min.js"]');
      browser.assert.element('script[src="vendor.min.js"]');

      browser.visit('/', function() {
        browser.assert.elements('script[src="app.min.js"]', {exactly: 0});
        browser.assert.elements('script[src="vendor.min.js"]', {exactly: 0});
        done();
      });
    });
  });
});

describe('Form for creating data', function() {
  this.timeout(25000);

  beforeEach(function(done) {
    browser.visit('/create', function() { done(); });
  });

  it('is alive', function(done) {
    browser.assert.success();
    done();
  });

  it('updates URL instantly with slugged name when it changes', function(done) {
    browser.fill('[data-editors=name] input[name=name]', 'This is the name');
    browser.fire('[data-editors=name] input[name=name]', 'keyup');
    browser.assert.text('[data-editors=name] [data-id=slug]', 'this-is-the-name');
    done();
  });

  it('has disabled submit button with default label reading "' + submitLabels.default + '"', function(done) {
    browser.assert.text('[data-id="submit-message"]', submitLabels.default);
    browser.assert.hasClass('[data-id="submit"]', 'form-button--disabled');
    done();
  });

  it('switches submit button into loading state when uploading a file/URL', function(done) {
    browser.attach('[data-id=upload] [data-id=file]', path.join(dataDir, 'decent.csv'));
    browser.assert.hasClass('[data-id="submit"]', 'form-button--loading');

    browser.visit('/create', function() {
      var
        upload = browser.window.APP.layout.createDp.layout.form.layout.upload;

      browser.fill('[data-id=upload] [data-id=link]', 'http://google.com');

      // There is no way to simulate pressing Enter, as .fire() doesn't support passing .event
      upload.events['keydown [data-id=link]'].call(upload, {keyCode: 13});

      browser.assert.hasClass('[data-id="submit"]', 'form-button--loading');
      done();
    });
  });

  it('uploads and validates valid CSV from local file', function(done) {
    assert(false);
  });

  it('uploads and validates malformed CSV from local file', function(done) {
    assert(false);
  });

  it('uploads and validates valid CSV from URL', function(done) {
    assert(false);
  });

  it('uploads and validates malformed CSV from URL', function(done) {
    assert(false);
  });

  it('allows passing to the next step when there is file and it is valid', function(done) {
    assert(false);
  });

  it('disallows passing to the next step when there is no file or it is invalid', function(done) {
    assert(false);
  });
});
<|endoftext|>


<commit_before>/**
 * PowerTip TooltipController
 *
 * @fileoverview  TooltipController object that manages tips for an instance.
 * @link          https://stevenbenner.github.io/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Creates a new tooltip controller.
 * @private
 * @constructor
 * @param {Object} options Options object containing settings.
 */
function TooltipController(options) {
	var placementCalculator = new PlacementCalculator(),
		tipElement = $('#' + options.popupId);

	// build and append tooltip div if it does not already exist
	if (tipElement.length === 0) {
		tipElement = $('<div/>', { id: options.popupId });
		// grab body element if it was not populated when the script loaded
		// note: this hack exists solely for jsfiddle support
		if ($body.length === 0) {
			$body = $('body');
		}
		$body.append(tipElement);
		// remember the tooltip elements that the plugin has created
		session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement;
	}

	// hook mousemove for cursor follow tooltips
	if (options.followMouse) {
		// only one positionTipOnCursor hook per tooltip element, please
		if (!tipElement.data(DATA_HASMOUSEMOVE)) {
			$document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor);
			$window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor);
			tipElement.data(DATA_HASMOUSEMOVE, true);
		}
	}

	/**
	 * Gives the specified element the active-hover state and queues up the
	 * showTip function.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function beginShowTip(element) {
		element.data(DATA_HASACTIVEHOVER, true);
		// show tooltip, asap
		tipElement.queue(function queueTipInit(next) {
			showTip(element);
			next();
		});
	}

	/**
	 * Shows the tooltip, as soon as possible.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function showTip(element) {
		var tipContent;

		// it is possible, especially with keyboard navigation, to move on to
		// another element with a tooltip during the queue to get to this point
		// in the code. if that happens then we need to not proceed or we may
		// have the fadeout callback for the last tooltip execute immediately
		// after this code runs, causing bugs.
		if (!element.data(DATA_HASACTIVEHOVER)) {
			return;
		}

		// if the tooltip is open and we got asked to open another one then the
		// old one is still in its fadeOut cycle, so wait and try again
		if (session.isTipOpen) {
			if (!session.isClosing) {
				hideTip(session.activeHover);
			}
			tipElement.delay(100).queue(function queueTipAgain(next) {
				showTip(element);
				next();
			});
			return;
		}

		// trigger powerTipPreRender event
		element.trigger('powerTipPreRender');

		// set tooltip content
		tipContent = getTooltipContent(element);
		if (tipContent) {
			tipElement.empty().append(tipContent);
		} else {
			// we have no content to display, give up
			return;
		}

		// trigger powerTipRender event
		element.trigger('powerTipRender');

		session.activeHover = element;
		session.isTipOpen = true;

		tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);

		// add custom class to tooltip element
		tipElement.addClass(options.popupClass);

		// set tooltip position
		// revert to static placement when the "force open" flag was set because
		// that flag means that we do not have accurate mouse position info
		if (!options.followMouse || element.data(DATA_FORCEDOPEN)) {
			positionTipOnElement(element);
			session.isFixedTipOpen = true;
		} else {
			positionTipOnCursor();
		}

		// close tooltip when clicking anywhere on the page, with the exception
		// of the tooltip's trigger element and any elements that are within a
		// tooltip that has 'mouseOnToPopup' option enabled
		// always enable this feature when the "force open" flag is set on a
		// followMouse tooltip because we reverted to static placement above
		if (!element.data(DATA_FORCEDOPEN) && !options.followMouse) {
			$document.on('click' + EVENT_NAMESPACE, function documentClick(event) {
				var target = event.target;
				if (target !== element[0]) {
					if (options.mouseOnToPopup) {
						if (target !== tipElement[0] && !$.contains(tipElement[0], target)) {
							$.powerTip.hide();
						}
					} else {
						$.powerTip.hide();
					}
				}
			});
		}

		// if we want to be able to mouse on to the tooltip then we need to
		// attach hover events to the tooltip that will cancel a close request
		// on mouseenter and start a new close request on mouseleave
		// only hook these listeners if we're not in manual mode
		if (options.mouseOnToPopup && !options.manual) {
			tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() {
				// check activeHover in case the mouse cursor entered the
				// tooltip during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
				}
			});
			tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() {
				// check activeHover in case the mouse cursor left the tooltip
				// during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
				}
			});
		}

		// fadein
		tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
			// start desync polling
			if (!session.desyncTimeout) {
				session.desyncTimeout = setInterval(closeDesyncedTip, 500);
			}

			// trigger powerTipOpen event
			element.trigger('powerTipOpen');
		});
	}

	/**
	 * Hides the tooltip.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function hideTip(element) {
		// reset session
		session.isClosing = true;
		session.isTipOpen = false;

		// stop desync polling
		session.desyncTimeout = clearInterval(session.desyncTimeout);

		// reset element state
		element.data(DATA_HASACTIVEHOVER, false);
		element.data(DATA_FORCEDOPEN, false);

		// remove document click handler
		$document.off('click' + EVENT_NAMESPACE);

		// unbind the mouseOnToPopup events if they were set
		tipElement.off(EVENT_NAMESPACE);

		// fade out
		tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
			var coords = new CSSCoordinates();

			// reset session and tooltip element
			session.activeHover = null;
			session.isClosing = false;
			session.isFixedTipOpen = false;
			tipElement.removeClass();

			// support mouse-follow and fixed position tips at the same time by
			// moving the tooltip to the last cursor location after it is hidden
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			tipElement.css(coords);

			// trigger powerTipClose event
			element.trigger('powerTipClose');
		});
	}

	/**
	 * Moves the tooltip to the users mouse cursor.
	 * @private
	 */
	function positionTipOnCursor() {
		var tipWidth,
			tipHeight,
			coords,
			collisions,
			collisionCount;

		// to support having fixed tooltips on the same page as cursor tooltips,
		// where both instances are referencing the same tooltip element, we
		// need to keep track of the mouse position constantly, but we should
		// only set the tip location if a fixed tip is not currently open, a tip
		// open is imminent or active, and the tooltip element in question does
		// have a mouse-follow using it.
		if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
			// grab measurements
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();
			coords = new CSSCoordinates();

			// grab collisions
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			collisions = getViewportCollisions(
				coords,
				tipWidth,
				tipHeight
			);

			// handle tooltip view port collisions
			if (collisions !== Collision.none) {
				collisionCount = countFlags(collisions);
				if (collisionCount === 1) {
					// if there is only one collision (bottom or right) then
					// simply constrain the tooltip to the view port
					if (collisions === Collision.right) {
						coords.set('left', session.windowWidth - tipWidth);
					} else if (collisions === Collision.bottom) {
						coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
					}
				} else {
					// if the tooltip has more than one collision then it is
					// trapped in the corner and should be flipped to get it out
					// of the users way
					coords.set('left', session.currentX - tipWidth - options.offset);
					coords.set('top', session.currentY - tipHeight - options.offset);
				}
			}

			// position the tooltip
			tipElement.css(coords);
		}
	}

	/**
	 * Sets the tooltip to the correct position relative to the specified target
	 * element. Based on options settings.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function positionTipOnElement(element) {
		var priorityList,
			finalPlacement;

		// when the followMouse option is enabled and the "force open" flag is
		// set we revert to static positioning. since the developer may not have
		// considered this scenario we should use smart placement
		if (options.smartPlacement || (options.followMouse && element.data(DATA_FORCEDOPEN))) {
			priorityList = $.fn.powerTip.smartPlacementLists[options.placement];

			// iterate over the priority list and use the first placement option
			// that does not collide with the view port. if they all collide
			// then the last placement in the list will be used.
			$.each(priorityList, function(idx, pos) {
				// place tooltip and find collisions
				var collisions = getViewportCollisions(
					placeTooltip(element, pos),
					tipElement.outerWidth(),
					tipElement.outerHeight()
				);

				// update the final placement variable
				finalPlacement = pos;

				// break if there were no collisions
				return collisions !== Collision.none;
			});
		} else {
			// if we're not going to use the smart placement feature then just
			// compute the coordinates and do it
			placeTooltip(element, options.placement);
			finalPlacement = options.placement;
		}

		// add placement as class for CSS arrows
		tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt');
		tipElement.addClass(finalPlacement);
	}

	/**
	 * Sets the tooltip position to the appropriate values to show the tip at
	 * the specified placement. This function will iterate and test the tooltip
	 * to support elastic tooltips.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
	 *     right position values.
	 */
	function placeTooltip(element, placement) {
		var iterationCount = 0,
			tipWidth,
			tipHeight,
			coords = new CSSCoordinates();

		// set the tip to 0,0 to get the full expanded width
		coords.set('top', 0);
		coords.set('left', 0);
		tipElement.css(coords);

		// to support elastic tooltips we need to check for a change in the
		// rendered dimensions after the tooltip has been positioned
		do {
			// grab the current tip dimensions
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();

			// get placement coordinates
			coords = placementCalculator.compute(
				element,
				placement,
				tipWidth,
				tipHeight,
				options.offset
			);

			// place the tooltip
			tipElement.css(coords);
		} while (
			// sanity check: limit to 5 iterations, and...
			++iterationCount <= 5 &&
			// try again if the dimensions changed after placement
			(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
		);

		return coords;
	}

	/**
	 * Checks for a tooltip desync and closes the tooltip if one occurs.
	 * @private
	 */
	function closeDesyncedTip() {
		var isDesynced = false;
		// It is possible for the mouse cursor to leave an element without
		// firing the mouseleave or blur event. This most commonly happens when
		// the element is disabled under mouse cursor. If this happens it will
		// result in a desynced tooltip because the tooltip was never asked to
		// close. So we should periodically check for a desync situation and
		// close the tip if such a situation arises.
		if (session.isTipOpen && !session.isClosing && !session.delayInProgress && ($.inArray('mouseleave', options.closeEvents) > -1 || $.inArray('mouseout', options.closeEvents) > -1 || $.inArray('blur', options.closeEvents) > -1 || $.inArray('focusout', options.closeEvents) > -1)) {
			// user moused onto another tip or active hover is disabled
			if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
				isDesynced = true;
			} else {
				// hanging tip - have to test if mouse position is not over the
				// active hover and not over a tooltip set to let the user
				// interact with it.
				// for keyboard navigation: this only counts if the element does
				// not have focus.
				// for tooltips opened via the api: we need to check if it has
				// the forcedOpen flag.
				if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
					if (tipElement.data(DATA_MOUSEONTOTIP)) {
						if (!isMouseOver(tipElement)) {
							isDesynced = true;
						}
					} else {
						isDesynced = true;
					}
				}
			}

			if (isDesynced) {
				// close the desynced tip
				hideTip(session.activeHover);
			}
		}
	}

	// expose methods
	this.showTip = beginShowTip;
	this.hideTip = hideTip;
	this.resetPosition = positionTipOnElement;
}
<commit_msg>Fixes the positioning of a following tooltip in a scrolled document<commit_after>/**
 * PowerTip TooltipController
 *
 * @fileoverview  TooltipController object that manages tips for an instance.
 * @link          https://stevenbenner.github.io/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

/**
 * Creates a new tooltip controller.
 * @private
 * @constructor
 * @param {Object} options Options object containing settings.
 */
function TooltipController(options) {
	var placementCalculator = new PlacementCalculator(),
		tipElement = $('#' + options.popupId);

	// build and append tooltip div if it does not already exist
	if (tipElement.length === 0) {
		tipElement = $('<div/>', { id: options.popupId });
		// grab body element if it was not populated when the script loaded
		// note: this hack exists solely for jsfiddle support
		if ($body.length === 0) {
			$body = $('body');
		}
		$body.append(tipElement);
		// remember the tooltip elements that the plugin has created
		session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement;
	}

	// hook mousemove for cursor follow tooltips
	if (options.followMouse) {
		// only one positionTipOnCursor hook per tooltip element, please
		if (!tipElement.data(DATA_HASMOUSEMOVE)) {
			$document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor);
			$window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor);
			tipElement.data(DATA_HASMOUSEMOVE, true);
		}
	}

	/**
	 * Gives the specified element the active-hover state and queues up the
	 * showTip function.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function beginShowTip(element) {
		element.data(DATA_HASACTIVEHOVER, true);
		// show tooltip, asap
		tipElement.queue(function queueTipInit(next) {
			showTip(element);
			next();
		});
	}

	/**
	 * Shows the tooltip, as soon as possible.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function showTip(element) {
		var tipContent;

		// it is possible, especially with keyboard navigation, to move on to
		// another element with a tooltip during the queue to get to this point
		// in the code. if that happens then we need to not proceed or we may
		// have the fadeout callback for the last tooltip execute immediately
		// after this code runs, causing bugs.
		if (!element.data(DATA_HASACTIVEHOVER)) {
			return;
		}

		// if the tooltip is open and we got asked to open another one then the
		// old one is still in its fadeOut cycle, so wait and try again
		if (session.isTipOpen) {
			if (!session.isClosing) {
				hideTip(session.activeHover);
			}
			tipElement.delay(100).queue(function queueTipAgain(next) {
				showTip(element);
				next();
			});
			return;
		}

		// trigger powerTipPreRender event
		element.trigger('powerTipPreRender');

		// set tooltip content
		tipContent = getTooltipContent(element);
		if (tipContent) {
			tipElement.empty().append(tipContent);
		} else {
			// we have no content to display, give up
			return;
		}

		// trigger powerTipRender event
		element.trigger('powerTipRender');

		session.activeHover = element;
		session.isTipOpen = true;

		tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);

		// add custom class to tooltip element
		tipElement.addClass(options.popupClass);

		// set tooltip position
		// revert to static placement when the "force open" flag was set because
		// that flag means that we do not have accurate mouse position info
		if (!options.followMouse || element.data(DATA_FORCEDOPEN)) {
			positionTipOnElement(element);
			session.isFixedTipOpen = true;
		} else {
			positionTipOnCursor();
		}

		// close tooltip when clicking anywhere on the page, with the exception
		// of the tooltip's trigger element and any elements that are within a
		// tooltip that has 'mouseOnToPopup' option enabled
		// always enable this feature when the "force open" flag is set on a
		// followMouse tooltip because we reverted to static placement above
		if (!element.data(DATA_FORCEDOPEN) && !options.followMouse) {
			$document.on('click' + EVENT_NAMESPACE, function documentClick(event) {
				var target = event.target;
				if (target !== element[0]) {
					if (options.mouseOnToPopup) {
						if (target !== tipElement[0] && !$.contains(tipElement[0], target)) {
							$.powerTip.hide();
						}
					} else {
						$.powerTip.hide();
					}
				}
			});
		}

		// if we want to be able to mouse on to the tooltip then we need to
		// attach hover events to the tooltip that will cancel a close request
		// on mouseenter and start a new close request on mouseleave
		// only hook these listeners if we're not in manual mode
		if (options.mouseOnToPopup && !options.manual) {
			tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() {
				// check activeHover in case the mouse cursor entered the
				// tooltip during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
				}
			});
			tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() {
				// check activeHover in case the mouse cursor left the tooltip
				// during the fadeOut and close cycle
				if (session.activeHover) {
					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
				}
			});
		}

		// fadein
		tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
			// start desync polling
			if (!session.desyncTimeout) {
				session.desyncTimeout = setInterval(closeDesyncedTip, 500);
			}

			// trigger powerTipOpen event
			element.trigger('powerTipOpen');
		});
	}

	/**
	 * Hides the tooltip.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function hideTip(element) {
		// reset session
		session.isClosing = true;
		session.isTipOpen = false;

		// stop desync polling
		session.desyncTimeout = clearInterval(session.desyncTimeout);

		// reset element state
		element.data(DATA_HASACTIVEHOVER, false);
		element.data(DATA_FORCEDOPEN, false);

		// remove document click handler
		$document.off('click' + EVENT_NAMESPACE);

		// unbind the mouseOnToPopup events if they were set
		tipElement.off(EVENT_NAMESPACE);

		// fade out
		tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
			var coords = new CSSCoordinates();

			// reset session and tooltip element
			session.activeHover = null;
			session.isClosing = false;
			session.isFixedTipOpen = false;
			tipElement.removeClass();

			// support mouse-follow and fixed position tips at the same time by
			// moving the tooltip to the last cursor location after it is hidden
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			tipElement.css(coords);

			// trigger powerTipClose event
			element.trigger('powerTipClose');
		});
	}

	/**
	 * Moves the tooltip to the users mouse cursor.
	 * @private
	 */
	function positionTipOnCursor() {
		var tipWidth,
			tipHeight,
			coords,
			collisions,
			collisionCount;

		// to support having fixed tooltips on the same page as cursor tooltips,
		// where both instances are referencing the same tooltip element, we
		// need to keep track of the mouse position constantly, but we should
		// only set the tip location if a fixed tip is not currently open, a tip
		// open is imminent or active, and the tooltip element in question does
		// have a mouse-follow using it.
		if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
			// grab measurements
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();
			coords = new CSSCoordinates();

			// grab collisions
			coords.set('top', session.currentY + options.offset);
			coords.set('left', session.currentX + options.offset);
			collisions = getViewportCollisions(
				coords,
				tipWidth,
				tipHeight
			);

			// handle tooltip view port collisions
			if (collisions !== Collision.none) {
				collisionCount = countFlags(collisions);
				if (collisionCount === 1) {
					// if there is only one collision (bottom or right) then
					// simply constrain the tooltip to the view port
					if (collisions === Collision.right) {
						coords.set('left', session.scrollLeft + session.windowWidth - tipWidth);
					} else if (collisions === Collision.bottom) {
						coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
					}
				} else {
					// if the tooltip has more than one collision then it is
					// trapped in the corner and should be flipped to get it out
					// of the users way
					coords.set('left', session.currentX - tipWidth - options.offset);
					coords.set('top', session.currentY - tipHeight - options.offset);
				}
			}

			// position the tooltip
			tipElement.css(coords);
		}
	}

	/**
	 * Sets the tooltip to the correct position relative to the specified target
	 * element. Based on options settings.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 */
	function positionTipOnElement(element) {
		var priorityList,
			finalPlacement;

		// when the followMouse option is enabled and the "force open" flag is
		// set we revert to static positioning. since the developer may not have
		// considered this scenario we should use smart placement
		if (options.smartPlacement || (options.followMouse && element.data(DATA_FORCEDOPEN))) {
			priorityList = $.fn.powerTip.smartPlacementLists[options.placement];

			// iterate over the priority list and use the first placement option
			// that does not collide with the view port. if they all collide
			// then the last placement in the list will be used.
			$.each(priorityList, function(idx, pos) {
				// place tooltip and find collisions
				var collisions = getViewportCollisions(
					placeTooltip(element, pos),
					tipElement.outerWidth(),
					tipElement.outerHeight()
				);

				// update the final placement variable
				finalPlacement = pos;

				// break if there were no collisions
				return collisions !== Collision.none;
			});
		} else {
			// if we're not going to use the smart placement feature then just
			// compute the coordinates and do it
			placeTooltip(element, options.placement);
			finalPlacement = options.placement;
		}

		// add placement as class for CSS arrows
		tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt');
		tipElement.addClass(finalPlacement);
	}

	/**
	 * Sets the tooltip position to the appropriate values to show the tip at
	 * the specified placement. This function will iterate and test the tooltip
	 * to support elastic tooltips.
	 * @private
	 * @param {jQuery} element The element that the tooltip should target.
	 * @param {string} placement The placement for the tooltip.
	 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
	 *     right position values.
	 */
	function placeTooltip(element, placement) {
		var iterationCount = 0,
			tipWidth,
			tipHeight,
			coords = new CSSCoordinates();

		// set the tip to 0,0 to get the full expanded width
		coords.set('top', 0);
		coords.set('left', 0);
		tipElement.css(coords);

		// to support elastic tooltips we need to check for a change in the
		// rendered dimensions after the tooltip has been positioned
		do {
			// grab the current tip dimensions
			tipWidth = tipElement.outerWidth();
			tipHeight = tipElement.outerHeight();

			// get placement coordinates
			coords = placementCalculator.compute(
				element,
				placement,
				tipWidth,
				tipHeight,
				options.offset
			);

			// place the tooltip
			tipElement.css(coords);
		} while (
			// sanity check: limit to 5 iterations, and...
			++iterationCount <= 5 &&
			// try again if the dimensions changed after placement
			(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
		);

		return coords;
	}

	/**
	 * Checks for a tooltip desync and closes the tooltip if one occurs.
	 * @private
	 */
	function closeDesyncedTip() {
		var isDesynced = false;
		// It is possible for the mouse cursor to leave an element without
		// firing the mouseleave or blur event. This most commonly happens when
		// the element is disabled under mouse cursor. If this happens it will
		// result in a desynced tooltip because the tooltip was never asked to
		// close. So we should periodically check for a desync situation and
		// close the tip if such a situation arises.
		if (session.isTipOpen && !session.isClosing && !session.delayInProgress && ($.inArray('mouseleave', options.closeEvents) > -1 || $.inArray('mouseout', options.closeEvents) > -1 || $.inArray('blur', options.closeEvents) > -1 || $.inArray('focusout', options.closeEvents) > -1)) {
			// user moused onto another tip or active hover is disabled
			if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
				isDesynced = true;
			} else {
				// hanging tip - have to test if mouse position is not over the
				// active hover and not over a tooltip set to let the user
				// interact with it.
				// for keyboard navigation: this only counts if the element does
				// not have focus.
				// for tooltips opened via the api: we need to check if it has
				// the forcedOpen flag.
				if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
					if (tipElement.data(DATA_MOUSEONTOTIP)) {
						if (!isMouseOver(tipElement)) {
							isDesynced = true;
						}
					} else {
						isDesynced = true;
					}
				}
			}

			if (isDesynced) {
				// close the desynced tip
				hideTip(session.activeHover);
			}
		}
	}

	// expose methods
	this.showTip = beginShowTip;
	this.hideTip = hideTip;
	this.resetPosition = positionTipOnElement;
}
<|endoftext|>


<commit_before>"use strict";
exports.__esModule = true;

var Notification = require("../").Notification;
var assert = require("chai").assert;
describe('Notification declaration', function () {
    it('should properly instanciate itself', function () {
        var n = new Notification('test', 'This is a test');
        // expect(n.app_name, 'n.app_name').equals('test', 'is set properly');
        // expect(n.body, 'n.body').equals('This is a test', 'is set properly');
        assert.instanceOf(n, Notification, 'notification has been defined');
        assert.strictEqual(n.app_name, 'test', 'app name to be set');
        assert.strictEqual(n.body, 'This is a test', "notification body to be set");
    });
    it('should properly rename app and/or details', function () {
        var n = new Notification('test', 'test');
        n.set_basic({ body: "This is the body", title: "Title", subtitle: "Subtitle" });
        // expect(n.title, 'notification title').equals("Title", 'to be properly set');
        // expect(n.subtitle, 'notification subtitle').equals('Subtitle', "to be properly set");
        // expect(n.body, 'notification body').equals('This is the body', 'to be properly set');
        assert.isNotNull(n.title, 'notification title has been defined');
        assert.isNotNull(n.subtitle, 'notification subtitle title has been defined');
        assert.strictEqual(n.title, 'Title', 'notification title is properly set');
        assert.strictEqual(n.subtitle, 'Subtitle', "notification subtitle to be properly set");
        assert.strictEqual(n.body, 'This is the body', "notification body has been properly set");
    });
    it('should accept adding actions', function () {
        var n = new Notification('test', 'this is a test');
        var act = {
            id: "test",
            name: "Test"
        };
        n.add_action(act);
        // expect(n.actions, 'n.actions').contains(act, 'contains the defined action');
        assert.isArray(n.actions, 'notifications actions are defined');
        assert.include(n.actions, act, "notification actions contains newly defined action");
    });
    it('should throw when setting reply details without setting reply mode first', function () {
        var n = new Notification('test', 'test');
        // expect(n.set_reply_message("Message content"), 'setting reply content throws').throws();
        assert["throw"](n.set_reply_message, "can't update message");
    });
    it('should be okay setting reply mode then editing reply message', function () {
        var n = new Notification('test', 'test');
        // expect(n.set_reply_message("New message content"), 'set new message content').ok();
        assert.ok(n.set_reply('Recipient', 'Message content to be overriden'), "Setting reply mode");
        assert.ok(n.set_reply_message("New message content"), "set new message content");
    });
});
<commit_msg>Completed tests<commit_after>"use strict";

var Notification = require("../").Notification;
var assert = require("chai").assert;
describe('The Notification class', function () {
    it('should properly instanciate itself', function () {
        var n = new Notification('test', 'This is a test');
        // expect(n.app_name, 'n.app_name').equals('test', 'is set properly');
        // expect(n.body, 'n.body').equals('This is a test', 'is set properly');
        assert.instanceOf(n, Notification, 'notification has been defined');
        assert.strictEqual(n.app_name, 'test', 'app name to be set');
        assert.strictEqual(n.body, 'This is a test', "notification body to be set");
    });
    it('should properly rename app and/or details', function () {
        var n = new Notification('test', 'test');
        n.set_basic({ body: "This is the body", title: "Title", subtitle: "Subtitle" });
        // expect(n.title, 'notification title').equals("Title", 'to be properly set');
        // expect(n.subtitle, 'notification subtitle').equals('Subtitle', "to be properly set");
        // expect(n.body, 'notification body').equals('This is the body', 'to be properly set');
        assert.isNotNull(n.title);
        assert.isNotNull(n.subtitle);
        assert.strictEqual(n.title, 'Title');
        assert.strictEqual(n.subtitle, 'Subtitle');
        assert.strictEqual(n.body, 'This is the body');
    });
    it('should accept adding actions', function () {
        var n = new Notification('test', 'this is a test');
        var act = {
            id: "test",
            name: "Test"
        };
        n.add_action(act);
        // expect(n.actions, 'n.actions').contains(act, 'contains the defined action');
        assert.isArray(n.actions);
        assert.include(n.actions, act);
    });
    it('should throw when adding spaces in the action id', () => {
        var n = new Notification('test', 'test');

    });
    it('should throw when setting reply details without setting reply mode first', function () {
        var n = new Notification('test', 'test');
        // expect(n.set_reply_message("Message content"), 'setting reply content throws').throws();
        assert.throw(() => {
            n.set_reply_message("Message content");
        }, "Should set notification as reply before setting message");
    });
    it('should be okay setting reply mode then editing reply message', function () {
        var n = new Notification('test', 'test');
        // expect(n.set_reply_message("New message content"), 'set new message content').ok();
        // assert.ok(n.set_reply('Recipient', 'Message content to be overriden'));
        // assert.ok(n.set_reply_message("New message content"));
        assert.doesNotThrow(() => {
            n.set_reply('Recipient', 'Message to be overriden');
            n.set_reply_message('New message');
        })
        assert.equal(n.isReply, true);
    });
});
<|endoftext|>


<commit_before>'use strict'

const Application = require('spectron').Application
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const path = require('path')
const fs = require('fs')

chai.should()
chai.use(chaiAsPromised)

describe('demo app', function () {
  this.timeout(30000)

  let app

  const removeStoredPreferences = function () {
    const productName = require('../package').productName
    const userDataPath = path.join(process.env.HOME, 'Library', 'Application Support', productName)
    try {
      fs.unlinkSync(path.join(userDataPath, 'activeDemoButtonId.json'))
    } catch (error) {
      if (error.code !== 'ENOENT') throw error
    }
    try {
      fs.unlinkSync(path.join(userDataPath, 'activeSectionButtonId.json'))
    } catch (error) {
      if (error.code !== 'ENOENT') throw error
    }
  }

  const setupApp = function (app) {
    chaiAsPromised.transferPromiseness = app.transferPromiseness
    return app.client.waitUntilWindowLoaded()
  }

  const startApp = function () {
    app = new Application({
      path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'),
      args: [
        path.join(__dirname, '..')
      ],
      waitTimeout: 10000
    })

    return app.start().then(setupApp)
  }

  const restartApp = function () {
    return app.restart().then(setupApp)
  }

  before(function () {
    removeStoredPreferences()
    return startApp()
  })

  after(function () {
    if (app && app.isRunning()) {
      return app.stop()
    }
  })

  it('opens a window displaying the about page', function () {
    return app.client.getWindowCount().should.eventually.equal(1)
      .browserWindow.isMinimized().should.eventually.be.false
      .browserWindow.isDevToolsOpened().should.eventually.be.false
      .browserWindow.isVisible().should.eventually.be.true
      .browserWindow.isFocused().should.eventually.be.true
      .browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
      .browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
      .getTitle().should.eventually.equal('Electron API Demos')
      .waitForVisible('#about-modal').should.eventually.be.true
      .isVisible('.js-nav').should.eventually.be.false
      .click('button[id="get-started"]').pause(500)
      .isVisible('#about-modal').should.eventually.be.false
      .isVisible('.js-nav').should.eventually.be.true
  })

  describe('when clicking on a section from the nav bar', function () {
    it('it shows the selected section in the main area', function () {
      return app.client.isVisible('#windows-section').should.eventually.be.true
        .click('button[data-section="windows"]').pause(100)
        .waitForVisible('#windows-section')
        .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.true
        .isVisible('#pdf-section').should.eventually.be.false
        .click('button[data-section="pdf"]').pause(100)
        .waitForVisible('#pdf-section')
        .isVisible('#windows-section').should.eventually.be.false
        .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.false
        .isExisting('button.is-selected[data-section="pdf"]').should.eventually.be.true
    })
  })

  describe('when a demo title is clicked', function () {
    it('it expands the demo content', function () {
      var onlyFirstVisible = Array(26).fill(false)
      onlyFirstVisible[0] = true

      return app.client.click('button[data-section="windows"]')
        .waitForVisible('#windows-section')
        .click('.js-container-target')
        .waitForVisible('.demo-box')
        .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible)
    })
  })

  describe('when the app is restarted after use', function () {
    it('it launches at last visted section & demo', function () {
      var onlyFirstVisible = Array(26).fill(false)
      onlyFirstVisible[0] = true

      return app.client.waitForVisible('#windows-section')
        .then(restartApp)
        .then(function () {
          return app.client.waitForVisible('#windows-section')
            .isVisible('#windows-section').should.eventually.be.true
            .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible)
        })
    })
  })
})
<commit_msg>update spec count<commit_after>'use strict'

const Application = require('spectron').Application
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const path = require('path')
const fs = require('fs')

chai.should()
chai.use(chaiAsPromised)

describe('demo app', function () {
  this.timeout(30000)

  let app

  const removeStoredPreferences = function () {
    const productName = require('../package').productName
    const userDataPath = path.join(process.env.HOME, 'Library', 'Application Support', productName)
    try {
      fs.unlinkSync(path.join(userDataPath, 'activeDemoButtonId.json'))
    } catch (error) {
      if (error.code !== 'ENOENT') throw error
    }
    try {
      fs.unlinkSync(path.join(userDataPath, 'activeSectionButtonId.json'))
    } catch (error) {
      if (error.code !== 'ENOENT') throw error
    }
  }

  const setupApp = function (app) {
    chaiAsPromised.transferPromiseness = app.transferPromiseness
    return app.client.waitUntilWindowLoaded()
  }

  const startApp = function () {
    app = new Application({
      path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'),
      args: [
        path.join(__dirname, '..')
      ],
      waitTimeout: 10000
    })

    return app.start().then(setupApp)
  }

  const restartApp = function () {
    return app.restart().then(setupApp)
  }

  before(function () {
    removeStoredPreferences()
    return startApp()
  })

  after(function () {
    if (app && app.isRunning()) {
      return app.stop()
    }
  })

  it('opens a window displaying the about page', function () {
    return app.client.getWindowCount().should.eventually.equal(1)
      .browserWindow.isMinimized().should.eventually.be.false
      .browserWindow.isDevToolsOpened().should.eventually.be.false
      .browserWindow.isVisible().should.eventually.be.true
      .browserWindow.isFocused().should.eventually.be.true
      .browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
      .browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
      .getTitle().should.eventually.equal('Electron API Demos')
      .waitForVisible('#about-modal').should.eventually.be.true
      .isVisible('.js-nav').should.eventually.be.false
      .click('button[id="get-started"]').pause(500)
      .isVisible('#about-modal').should.eventually.be.false
      .isVisible('.js-nav').should.eventually.be.true
  })

  describe('when clicking on a section from the nav bar', function () {
    it('it shows the selected section in the main area', function () {
      return app.client.isVisible('#windows-section').should.eventually.be.true
        .click('button[data-section="windows"]').pause(100)
        .waitForVisible('#windows-section')
        .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.true
        .isVisible('#pdf-section').should.eventually.be.false
        .click('button[data-section="pdf"]').pause(100)
        .waitForVisible('#pdf-section')
        .isVisible('#windows-section').should.eventually.be.false
        .isExisting('button.is-selected[data-section="windows"]').should.eventually.be.false
        .isExisting('button.is-selected[data-section="pdf"]').should.eventually.be.true
    })
  })

  describe('when a demo title is clicked', function () {
    it('it expands the demo content', function () {
      var onlyFirstVisible = Array(27).fill(false)
      onlyFirstVisible[0] = true

      return app.client.click('button[data-section="windows"]')
        .waitForVisible('#windows-section')
        .click('.js-container-target')
        .waitForVisible('.demo-box')
        .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible)
    })
  })

  describe('when the app is restarted after use', function () {
    it('it launches at last visted section & demo', function () {
      var onlyFirstVisible = Array(27).fill(false)
      onlyFirstVisible[0] = true

      return app.client.waitForVisible('#windows-section')
        .then(restartApp)
        .then(function () {
          return app.client.waitForVisible('#windows-section')
            .isVisible('#windows-section').should.eventually.be.true
            .isVisible('.demo-box').should.eventually.deep.equal(onlyFirstVisible)
        })
    })
  })
})
<|endoftext|>