<commit_before>/**
 * Client
 * Cassius - https://github.com/sirDonovan/Cassius
 *
 * This file connects to the server and handles incoming/outgoing messages.
 *
 * @license MIT license
 */

'use strict';

const WebSocketClient = require('websocket').client;
const https = require('https');
const url = require('url');
const querystring = require('querystring');
const Room = require('./rooms').Room; // eslint-disable-line no-unused-vars
const MESSAGE_THROTTLE = 600;
const RETRY_SECONDS = 60;

let server = 'play.pokemonshowdown.com';
if (Config.server && Config.server !== server) {
	server = Config.server.includes('.psim.us') ? Config.server : Config.server + '.psim.us';
}
let serverId = 'showdown';

class Client {
	constructor() {
		this.challengeKeyId = '';
		this.challenge = '';
		/**@type {Array<string>} */
		this.messageQueue = [];
		this.messageQueueTimeout = null;

		this.client = new WebSocketClient();
		this.client.on('connect', connection => {
			this.connection = connection;

			this.connection.on('message', message => {
				if (message.type !== 'utf8' || !message.utf8Data || message.utf8Data.charAt(0) !== 'a') return;
				this.onMessage(message.utf8Data);
			});

			this.connection.on('error', error => console.log('Connection error: ' + error.stack));

			this.connection.on('close', (code, description) => {
				console.log('Connection closed: ' + description + ' (' + code + ')\nReconnecting in ' + RETRY_SECONDS + ' seconds');
				setTimeout(() => this.connect(), RETRY_SECONDS * 1000);
			});

			this.onConnect();
		});
		this.client.on('connectFailed', error => this.onConnectFail(error));
	}

	onConnect() {
		console.log('Successfully connected to server ' + server);
	}

	/**
	 * @param {Error} error
	 */
	onConnectFail(error) {
		console.log('Failed to connect to server ' + server + ':\n' + error.stack + '\nRetrying in ' + RETRY_SECONDS + ' seconds');
		setTimeout(() => this.connect(), RETRY_SECONDS * 1000);
	}

	connect() {
		let characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_'];
		let string = '';
		for (let i = 0, len = characters.length; i < 8; i++) {
			string += characters[Math.floor((Math.random() * len))];
		}

		let options = {
			hostname: 'play.pokemonshowdown.com',
			path: '/crossdomain.php?' + querystring.stringify({host: server, path: ''}),
			method: 'GET',
		};

		https.get(options, response => {
			response.setEncoding('utf8');
			let data = '';
			response.on('data', chunk => {
				data += chunk;
			});
			response.on('end', () => {
				let configData = data.split('var config = ')[1];
				if (configData) {
					let config = JSON.parse(configData.split(';')[0]);
					if (typeof config === 'string') config = JSON.parse(config); // encoded twice by the server
					if (config.host) {
						if (config.id) serverId = config.id;
						this.client.connect('ws://' + (config.host === 'showdown' ? 'sim.smogon.com' : config.host) + ':' + (config.port || 8000) + '/showdown/' + Math.floor(Math.random() * 1000) + '/' + string + '/websocket');
						return;
					}
				}
				console.log('Error: failed to get data for server ' + server);
			});
		}).on('error', error => {
			console.log('Error: ' + error.message);
		});
	}

	/**
	 * @param {string} messageData
	 */
	onMessage(messageData) {
		/**@type {Array<string>} */
		let message = JSON.parse(messageData.substr(1));
		if (!(message instanceof Array)) message = [message];
		for (let i = 0, len = message.length; i < len; i++) {
			if (!message[i]) continue;
			let room = Rooms.add('lobby');
			if (!message[i].includes('\n')) return this.parseMessage(message[i], room);

			let lines = message[i].split('\n');
			if (lines[0].charAt(0) === '>') {
				room = Rooms.add(lines[0].substr(1));
				lines.shift();
			}
			for (let i = 0, len = lines.length; i < len; i++) {
				if (lines[i].startsWith('|init|')) {
					this.parseMessage(lines[i], room);
					lines = lines.slice(i + 1);
					for (let i = 0, len = lines.length; i < len; i++) {
						if (lines[i].startsWith('|users|')) {
							this.parseMessage(lines[i], room);
							break;
						}
					}
					return;
				}
				this.parseMessage(lines[i], room);
			}
		}
	}

	/**
	 * @param {string} message
	 * @param {Room} room
	 */
	parseMessage(message, room) {
		let splitMessage = message.split('|').slice(1);
		let messageType = splitMessage[0];
		splitMessage.shift();
		if (typeof Config.parseMessage === 'function') {
			if (Config.parseMessage(room, messageType, splitMessage) === false) return;
		}
		switch (messageType) {
		case 'challstr':
			this.challengeKeyId = splitMessage[0];
			this.challenge = splitMessage[1];
			this.login();
			break;
		case 'updateuser':
			if (splitMessage[0] !== Config.username) return;
			if (splitMessage[1] !== '1') {
				console.log('Failed to log in');
				process.exit();
			}

			console.log('Successfully logged in');
			if (Config.rooms) {
				if (!(Config.rooms instanceof Array)) throw new Error("Config.rooms must be an array");
				for (let i = 0, len = Config.rooms.length; i < len; i++) {
					this.send('|/join ' + Config.rooms[i]);
				}
			}
			break;
		case 'init':
			room.onJoin(Users.self, ' ');
			console.log('Joined room: ' + room.id);
			break;
		case 'noinit':
			console.log('Could not join room: ' + room.id);
			Rooms.destroy(room);
			break;
		case 'deinit':
			Rooms.destroy(room);
			break;
		case 'users':
			if (splitMessage[0] === '0') return;
			let users = splitMessage[0].split(",");
			for (let i = 1, len = users.length; i < len; i++) {
				let user = Users.add(users[i].substr(1));
				let rank = users[i].charAt(0);
				room.users.set(user, rank);
				user.rooms.set(room, rank);
			}
			break;
		case 'pm':
			let user = Users.add(splitMessage[0]);
			if (!user) return;
			if (user.id === Users.self.id) return;
			MessageParser.parseCommand(splitMessage.slice(2).join('|'), user, user);
			break;
		default:
			room.parseMessage(messageType, splitMessage);
		}
	}

	login() {
		let action = url.parse('https://play.pokemonshowdown.com/~~' + serverId + '/action.php');
		let options = {
			hostname: action.hostname,
			path: action.pathname,
			agent: false,
		};
		if (action.port) options.port = parseInt(action.port);

		let postData;
		if (Config.password) {
			options.method = 'POST';
			postData = querystring.stringify({
				'act': 'login',
				'name': Config.username,
				'pass': Config.password,
				'challengekeyid': this.challengeKeyId,
				'challenge': this.challenge,
			});
			options.headers = {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': postData.length,
			};
		} else {
			options.method = 'GET';
			options.path += '?' + querystring.stringify({
				'act': 'getassertion',
				'userid': Tools.toId(Config.username),
				'challengekeyid': this.challengeKeyId,
				'challenge': this.challenge,
			});
		}

		let request = https.request(options, response => {
			response.setEncoding('utf8');
			let data = '';
			response.on('data', chunk => {
				data += chunk;
			});
			response.on('end', () => {
				if (data === ';') {
					console.log('Failed to log in: invalid password');
					process.exit();
				} else if (data.startsWith('<!DOCTYPE html>')) {
					console.log('Failed to log in: connection timed out. Trying again in ' + RETRY_SECONDS + ' seconds');
					setTimeout(() => this.login(), RETRY_SECONDS * 1000);
					return;
				} else if (data.includes('heavy load')) {
					console.log('Failed to log in: the login server is under heavy load. Trying again in ' + RETRY_SECONDS + ' seconds');
					setTimeout(() => this.login(), RETRY_SECONDS * 1000);
					return;
				} else if (data.length < 50) {
					console.log('Failed to log in: ' + data);
					process.exit();
				} else {
					if (Config.password) {
						let assertion = JSON.parse(data.substr(1));
						if (assertion.actionsuccess) {
							data = assertion.assertion;
						} else {
							console.log('Failed to log in: ' + JSON.stringify(assertion));
							process.exit();
						}
					}
					this.send('|/trn ' + Config.username + ',0,' + data);
				}
			});
		});

		request.on('error', error => console.log('Login error: ' + error.stack));

		if (postData) request.write(postData);
		request.end();
	}

	/**
	 * @param {string} message
	 */
	send(message) {
		if (!message || !this.connection || !this.connection.connected) return;
		if (this.messageQueueTimeout) {
			this.messageQueue.push(message);
			return;
		}
		message = JSON.stringify([message]);
		this.connection.send(message);
		this.messageQueueTimeout = setTimeout(() => {
			this.messageQueueTimeout = null;
			let message = this.messageQueue.shift();
			if (message) this.send(message);
		}, MESSAGE_THROTTLE);
	}
}

module.exports = new Client();
<commit_msg>Client: reconnect on connection error<commit_after>/**
 * Client
 * Cassius - https://github.com/sirDonovan/Cassius
 *
 * This file connects to the server and handles incoming/outgoing messages.
 *
 * @license MIT license
 */

'use strict';

const WebSocketClient = require('websocket').client;
const https = require('https');
const url = require('url');
const querystring = require('querystring');
const Room = require('./rooms').Room; // eslint-disable-line no-unused-vars
const MESSAGE_THROTTLE = 600;
const RETRY_SECONDS = 60;

let server = 'play.pokemonshowdown.com';
if (Config.server && Config.server !== server) {
	server = Config.server.includes('.psim.us') ? Config.server : Config.server + '.psim.us';
}
let serverId = 'showdown';

class Client {
	constructor() {
		this.challengeKeyId = '';
		this.challenge = '';
		/**@type {Array<string>} */
		this.messageQueue = [];
		this.messageQueueTimeout = null;
		this.connectTimeout = null;

		this.client = new WebSocketClient();
		this.client.on('connect', connection => {
			this.connection = connection;

			this.connection.on('message', message => this.onMessage(message));
			this.connection.on('error', error => this.onConnectionError(error));
			this.connection.on('close', (code, description) => this.onConnectionClose(code, description));

			this.onConnect();
		});
		this.client.on('connectFailed', error => this.onConnectFail(error));
	}

	onConnect() {
		console.log('Successfully connected to server ' + server);
	}

	/**
	 * @param {Error} error
	 */
	onConnectFail(error) {
		if (this.connectTimeout) clearTimeout(this.connectTimeout);
		console.log('Failed to connect to server ' + server + ':\n' + error.stack + '\nRetrying in ' + RETRY_SECONDS + ' seconds');
		this.connectTimeout = setTimeout(() => this.connect(), RETRY_SECONDS * 1000);
	}

	/**
	 * @param {Error} error
	 */
	onConnectionError(error) {
		if (this.connectTimeout) clearTimeout(this.connectTimeout);
		console.log('Connection error: ' + error.stack);
		this.connectTimeout = setTimeout(() => this.connect(), RETRY_SECONDS * 1000);
	}

	/**
	 * @param {number} code
	 * @param {string} description
	 */
	onConnectionClose(code, description) {
		if (this.connectTimeout) clearTimeout(this.connectTimeout);
		console.log('Connection closed: ' + description + ' (' + code + ')\nReconnecting in ' + RETRY_SECONDS + ' seconds');
		this.connectTimeout = setTimeout(() => this.connect(), RETRY_SECONDS * 1000);
	}

	connect() {
		let characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_'];
		let string = '';
		for (let i = 0, len = characters.length; i < 8; i++) {
			string += characters[Math.floor((Math.random() * len))];
		}

		let options = {
			hostname: 'play.pokemonshowdown.com',
			path: '/crossdomain.php?' + querystring.stringify({host: server, path: ''}),
			method: 'GET',
		};

		https.get(options, response => {
			response.setEncoding('utf8');
			let data = '';
			response.on('data', chunk => {
				data += chunk;
			});
			response.on('end', () => {
				let configData = data.split('var config = ')[1];
				if (configData) {
					let config = JSON.parse(configData.split(';')[0]);
					if (typeof config === 'string') config = JSON.parse(config); // encoded twice by the server
					if (config.host) {
						if (config.id) serverId = config.id;
						this.client.connect('ws://' + (config.host === 'showdown' ? 'sim.smogon.com' : config.host) + ':' + (config.port || 8000) + '/showdown/' + Math.floor(Math.random() * 1000) + '/' + string + '/websocket');
						return;
					}
				}
				console.log('Error: failed to get data for server ' + server);
			});
		}).on('error', error => {
			console.log('Error: ' + error.message);
		});
	}

	/**
	 * @param {{type: string, utf8Data?: string}} incomingMessage
	 */
	onMessage(incomingMessage) {
		if (incomingMessage.type !== 'utf8' || !incomingMessage.utf8Data || incomingMessage.utf8Data.charAt(0) !== 'a') return;
		/**@type {Array<string>} */
		let message = JSON.parse(incomingMessage.utf8Data.substr(1));
		if (!(message instanceof Array)) message = [message];
		for (let i = 0, len = message.length; i < len; i++) {
			if (!message[i]) continue;
			let room = Rooms.add('lobby');
			if (!message[i].includes('\n')) return this.parseMessage(message[i], room);

			let lines = message[i].split('\n');
			if (lines[0].charAt(0) === '>') {
				room = Rooms.add(lines[0].substr(1));
				lines.shift();
			}
			for (let i = 0, len = lines.length; i < len; i++) {
				if (lines[i].startsWith('|init|')) {
					this.parseMessage(lines[i], room);
					lines = lines.slice(i + 1);
					for (let i = 0, len = lines.length; i < len; i++) {
						if (lines[i].startsWith('|users|')) {
							this.parseMessage(lines[i], room);
							break;
						}
					}
					return;
				}
				this.parseMessage(lines[i], room);
			}
		}
	}

	/**
	 * @param {string} message
	 * @param {Room} room
	 */
	parseMessage(message, room) {
		let splitMessage = message.split('|').slice(1);
		let messageType = splitMessage[0];
		splitMessage.shift();
		if (typeof Config.parseMessage === 'function') {
			if (Config.parseMessage(room, messageType, splitMessage) === false) return;
		}
		switch (messageType) {
		case 'challstr':
			this.challengeKeyId = splitMessage[0];
			this.challenge = splitMessage[1];
			this.login();
			break;
		case 'updateuser':
			if (splitMessage[0] !== Config.username) return;
			if (splitMessage[1] !== '1') {
				console.log('Failed to log in');
				process.exit();
			}

			console.log('Successfully logged in');
			if (Config.rooms) {
				if (!(Config.rooms instanceof Array)) throw new Error("Config.rooms must be an array");
				for (let i = 0, len = Config.rooms.length; i < len; i++) {
					this.send('|/join ' + Config.rooms[i]);
				}
			}
			break;
		case 'init':
			room.onJoin(Users.self, ' ');
			console.log('Joined room: ' + room.id);
			break;
		case 'noinit':
			console.log('Could not join room: ' + room.id);
			Rooms.destroy(room);
			break;
		case 'deinit':
			Rooms.destroy(room);
			break;
		case 'users':
			if (splitMessage[0] === '0') return;
			let users = splitMessage[0].split(",");
			for (let i = 1, len = users.length; i < len; i++) {
				let user = Users.add(users[i].substr(1));
				let rank = users[i].charAt(0);
				room.users.set(user, rank);
				user.rooms.set(room, rank);
			}
			break;
		case 'pm':
			let user = Users.add(splitMessage[0]);
			if (!user) return;
			if (user.id === Users.self.id) return;
			MessageParser.parseCommand(splitMessage.slice(2).join('|'), user, user);
			break;
		default:
			room.parseMessage(messageType, splitMessage);
		}
	}

	login() {
		let action = url.parse('https://play.pokemonshowdown.com/~~' + serverId + '/action.php');
		let options = {
			hostname: action.hostname,
			path: action.pathname,
			agent: false,
		};
		if (action.port) options.port = parseInt(action.port);

		let postData;
		if (Config.password) {
			options.method = 'POST';
			postData = querystring.stringify({
				'act': 'login',
				'name': Config.username,
				'pass': Config.password,
				'challengekeyid': this.challengeKeyId,
				'challenge': this.challenge,
			});
			options.headers = {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': postData.length,
			};
		} else {
			options.method = 'GET';
			options.path += '?' + querystring.stringify({
				'act': 'getassertion',
				'userid': Tools.toId(Config.username),
				'challengekeyid': this.challengeKeyId,
				'challenge': this.challenge,
			});
		}

		let request = https.request(options, response => {
			response.setEncoding('utf8');
			let data = '';
			response.on('data', chunk => {
				data += chunk;
			});
			response.on('end', () => {
				if (data === ';') {
					console.log('Failed to log in: invalid password');
					process.exit();
				} else if (data.startsWith('<!DOCTYPE html>')) {
					console.log('Failed to log in: connection timed out. Trying again in ' + RETRY_SECONDS + ' seconds');
					setTimeout(() => this.login(), RETRY_SECONDS * 1000);
					return;
				} else if (data.includes('heavy load')) {
					console.log('Failed to log in: the login server is under heavy load. Trying again in ' + RETRY_SECONDS + ' seconds');
					setTimeout(() => this.login(), RETRY_SECONDS * 1000);
					return;
				} else if (data.length < 50) {
					console.log('Failed to log in: ' + data);
					process.exit();
				} else {
					if (Config.password) {
						let assertion = JSON.parse(data.substr(1));
						if (assertion.actionsuccess) {
							data = assertion.assertion;
						} else {
							console.log('Failed to log in: ' + JSON.stringify(assertion));
							process.exit();
						}
					}
					this.send('|/trn ' + Config.username + ',0,' + data);
				}
			});
		});

		request.on('error', error => console.log('Login error: ' + error.stack));

		if (postData) request.write(postData);
		request.end();
	}

	/**
	 * @param {string} message
	 */
	send(message) {
		if (!message || !this.connection || !this.connection.connected) return;
		if (this.messageQueueTimeout) {
			this.messageQueue.push(message);
			return;
		}
		message = JSON.stringify([message]);
		this.connection.send(message);
		this.messageQueueTimeout = setTimeout(() => {
			this.messageQueueTimeout = null;
			let message = this.messageQueue.shift();
			if (message) this.send(message);
		}, MESSAGE_THROTTLE);
	}
}

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


<commit_before>import constant from 'lodash.constant';

import Rect from 'maths/rect';
import Vector from 'maths/vector';

export default function VerletModel(size, {x: px0, y: py0} = {x: 0, y: 0}, {x: vx0, y: vy0} = {x: 0, y: 0}) {
	const current = Vector({x: px0, y: py0});
	const velocity = Vector({x: vx0, y: vy0});
	const previous = current.sub(velocity);
	const rect = Rect(current, size);

	return {
		position() {
			return current;
		},
		setPosition({x, y}) {
			current.x = rect.x = x;
			current.y = rect.y = y;
			previous.x = current.x - velocity.x;
			previous.y = current.y - velocity.y;
			return this;
		},
		velocity() {
			return velocity;
		},
		setVelocity({x, y}) {
			velocity.x = x;
			velocity.y = y;
			previous.x = current.x - x;
			previous.y = current.y - y;
			return this;
		},
		update() {
			this.setPosition({
				x: current.x + velocity.x,
				y: current.y + velocity.y
			});
		},
		localRect: constant(Rect(Vector.Null, size)),
		rect() {
			return rect;
		},
		size() {
			return rect.size;
		},
		setSize({width, height}) {
			rect.width = width;
			rect.height = height;
		}
	};
}
<commit_msg>Fix setSize<commit_after>import Rect from 'maths/rect';
import Vector from 'maths/vector';

export default function VerletModel(size, {x: px0, y: py0} = {x: 0, y: 0}, {x: vx0, y: vy0} = {x: 0, y: 0}) {
	const current = Vector({x: px0, y: py0});
	const velocity = Vector({x: vx0, y: vy0});
	const previous = current.sub(velocity);
	const rect = Rect(current, size);
	const localRect = Rect(Vector.Null, size);

	return {
		position() {
			return current;
		},
		setPosition({x, y}) {
			current.x = rect.x = x;
			current.y = rect.y = y;
			previous.x = current.x - velocity.x;
			previous.y = current.y - velocity.y;
			return this;
		},
		velocity() {
			return velocity;
		},
		setVelocity({x, y}) {
			velocity.x = x;
			velocity.y = y;
			previous.x = current.x - x;
			previous.y = current.y - y;
			return this;
		},
		update() {
			this.setPosition({
				x: current.x + velocity.x,
				y: current.y + velocity.y
			});
		},
		localRect() {
			return localRect;
		},
		rect() {
			return rect;
		},
		size() {
			return rect.size;
		},
		setSize({width, height}) {
			rect.width = width;
			localRect.width = width;
			rect.height = height;
			localRect.height = height;
		}
	};
}
<|endoftext|>


<commit_before>exports = module.exports = function(IoC, gateways, service, settings, logger) {
  var normalizePort = require('../lib/utils').normalizePort;

  // TODO: Dynamically create the Application object, and if it is not available,
  //       fallback to loading an internal one, which automatically `use`s
  //       all available services.
  //       ie, it will automatically put the /oauth and /oauth/mfa etc services
  //       in place

  return Promise.resolve()
    /*
    .then(IoC.create.bind(IoC, './routes'))
    .then(function(routes) {
      routes.apply(service);
    })
    .catch(function(err) {
      // FIXME: Test for error code
      if (err.message.indexOf('Unable to create') == 0) {
        return;
      }
      throw err;
    })
    */
    .then(function() {
      var gatewayIfaces = IoC.components(gateways());

      return Promise.all(gatewayIfaces.map(function(iface) { return iface.create(); } ))
        .then(function(gateways) {
          gateways.forEach(function(gateway, i) {
            //console.log(gateway);
            //console.log(i);

            var server = gateway;

            // Dispatch requests to the service, which in this case is an Express app.
            server.on('request', service);

            server.once('listening', function() {
              var addr = this.address();
              logger.info('HTTP server listening on %s:%d', addr.address, addr.port);
            });

            var opts = settings.get('http/server') || {};

            var address = opts.address;
            var port = opts.port !== undefined ? opts.port : (normalizePort(process.env.PORT) || 8080);

            server.listen(port, address);

          });
        });
    });
};

exports['@singleton'] = true;
exports['@require'] = [
  '!container',
  './gateways',
  'http://i.bixbyjs.org/http/Service',
  'http://i.bixbyjs.org/Settings',
  'http://i.bixbyjs.org/Logger'
];
<commit_msg>Just hook up request dispatch, moving port binding to transport layer in bixby-http.<commit_after>exports = module.exports = function(IoC, gateways, service, settings, logger) {
  var normalizePort = require('../lib/utils').normalizePort;

  // TODO: Dynamically create the Application object, and if it is not available,
  //       fallback to loading an internal one, which automatically `use`s
  //       all available services.
  //       ie, it will automatically put the /oauth and /oauth/mfa etc services
  //       in place

  return Promise.resolve()
    /*
    .then(IoC.create.bind(IoC, './routes'))
    .then(function(routes) {
      routes.apply(service);
    })
    .catch(function(err) {
      // FIXME: Test for error code
      if (err.message.indexOf('Unable to create') == 0) {
        return;
      }
      throw err;
    })
    */
    .then(function() {
      var gatewayIfaces = IoC.components(gateways());

      return Promise.all(gatewayIfaces.map(function(iface) { return iface.create(); } ))
        .then(function(gateways) {
          gateways.forEach(function(gateway, i) {
            // Dispatch requests to the service, which in this case is an Express app.
            gateway.on('request', service);
          });
        });
    });
};

exports['@singleton'] = true;
exports['@require'] = [
  '!container',
  './gateways',
  'http://i.bixbyjs.org/http/Service',
  'http://i.bixbyjs.org/Settings',
  'http://i.bixbyjs.org/Logger'
];
<|endoftext|>


<commit_before>/*jslint laxcomma: true, loopfunc: true */
module.exports = function (grunt) {

    grunt.initConfig({
        // We want to generate 3 new files, which are part of the release build...
        concat: {
            // With timer, which is Tempus and Tempus.Timer in one export
            with_timer: {
                src: [
                    'tempus.js'
                ,   'tempus.timer.js'
                ],
                dest: './tempus.with.timer.js'
            },
            // With interval, which is Tempus and Tempus.Interval in one export
            with_interval: {
                src: [
                    'tempus.js'
                ,   'tempus.interval.js'
                ],
                dest: './tempus.with.interval.js'
            },
            // With timer and interval; both modules inside one export with Tempus
            with_timer_and_interval: {
                src: [
                    'tempus.js'
                ,   'tempus.interval.js'
                ,   'tempus.timer.js'
                ],
                dest: './tempus.with.timer.and.interval.js'
            }
        },
        // We want the 3 main JS files to be linted, and the concated ones
        lint: {
            all: [
                'tempus.js'
            ,   'tempus.interval.js'
            ,   'tempus.timer.js'
            ],
            concated: [
                'tempus.with.timer.js'
            ,   'tempus.with.interval.js'
            ,   'tempus.with.interval.and.timer.js'
            ]
        },
        min: {
            // Minifiy Tempus JS, and also specify a target size when gzipped
            tempus: {
                src: ['tempus.js'],
                dest: 'tempus.min.js',
                size_target: 4505
            },
            // Minifiy Tempus.Timer
            tempus_timer: {
                src: ['tempus.timer.js'],
                dest: 'tempus.timer.min.js'
            },
            // Minify Tempus.Interval
            tempus_interval: {
                src: ['tempus.interval.js'],
                dest: 'tempus.interval.min.js'
            },
            // Minify our concated tempus.with.timer
            tempus_with_timer: {
                src: ['tempus.with.timer.js'],
                dest: 'tempus.with.timer.min.js'
            },
            // Minify our concated tempus.with.interval
            tempus_with_interval: {
                src: ['tempus.with.interval.js'],
                dest: 'tempus.with.interval.min.js'
            },
            // Minify our concated tempus.with.timer.and.interval
            // with a target size of 7k
            tempus_with_timer_and_interval: {
                src: ['tempus.with.timer.and.interval.js'],
                dest: 'tempus.with.timer.and.interval.min.js',
                size_target: 7168
            }
        },
        // We want to make sure that UglifyJS compresses as much as it can
        uglify: {
            mangle: { top_level: true }
        },
        // We can load in the QUnit test files for phantom.
        qunit: {
            index: ['test/index.html']
        },
        // We'll load in the specific files Node-QUnit will test:
        'qunit-node': {
            testFiles:
                [   'utils'
                ,   'creation'
                ,   'originalMethods'
                ,   'dateMethods'
                ,   'utilityMethods'
                ,   'yearMethods'
                ,   'monthMethods'
                ,   'weekMethods'
                ,   'dayMethods'
                ,   'timeMethods'
                ,   'timezoneMethods'
                ,   'utcMethods'
                ,   'stringMethods'
                ,   'highLevelFunctionality'
                ,   'coremethods'
                ,   'timer'
                ]
        }
    });

    /*******************************************************/
    /************************ TASKS ************************/
    /*******************************************************/

    // We want to overload the min_max_info helper, which is log output
    // minify task. We're overloading it to work with the size_target
    // delcaration in the initConfig
    grunt.registerHelper('min_max_info', function(min, max) {
        var gzipSize     = String(grunt.helper('gzip', min).length)
        ,   expectedSize = grunt.task.current.data.size_target || false;

        grunt.log.writeln('Uncompressed size: ' + String(max.length).green + ' bytes.');

        // If size_target doesn't exist, do what min_max_info normally does.
        if (!expectedSize) {
            grunt.log.writeln('Compressed size: ' + gzipSize.green + ' bytes gzipped '+
                '(' + String(min.length).green + ' bytes minified).');

            return;
        }

        // Get the difference in bytes between the two sizes
        var diff = Math.abs(expectedSize - gzipSize);

        // If we're over, flash up a RED size and say we're over
        if (gzipSize > expectedSize) {
            grunt.log.error('Compressed size: ' + gzipSize.red + ' bytes ' +
                '(' + String(diff).red + ' bytes over ' + expectedSize + ' byte target) gzipped ' +
                '(' + String(min.length).green + ' bytes minified).');

        // If we're under, flash up a GREEN size and say we're under
        } else {
            grunt.log.writeln('Compressed size: ' + gzipSize.green + ' bytes ' +
                '(' + String(diff).green + ' bytes under ' + expectedSize + ' byte target) gzipped ' +
                '(' + String(min.length).green + ' bytes minified).');
        }
    });

    // We want to overload the concat helpr, which is the meat of the concat task.
    // We're overloading it to strip down the closures of the modules and to insert
    // them into the core's closure, for a better, smaller codebase
    grunt.registerHelper('concat', function(files, options) {
        options = grunt.utils._.defaults(options || {}, {
            separator: grunt.utils.linefeed
        });

        // Get the first file to inject all subsequent files into.
        parent_file = grunt.task.directive(files.shift(), grunt.file.read);

        // Replace the bit which says "// <% MODULE_INJECTION %> //" in the first file, with
        // the stripped down contents of all remaining files....
        return parent_file.replace(/\s+\/\/ <% MODULE_INJECTION %> \/\//, files ? files.map(function(f) {
            var file = grunt.task.directive(f, grunt.file.read);

            // Find the closure function declaration ...
            var match = file.match(/\(function \(.*\n/);
            if (match) {
                // ... and remove everything before it ...
                file = file.substr(match.index)
                    // ... plus the declaration itself, and the next line,
                    // and the last line.
                    .split("\n").slice(2, -1).join("\n");
            }
            // Add a new line plus file comment to the top for formatting
            return "\n\n\t// " + f + " injected\n" + file + "\n\t// End " + f + "\n";
        }).join(grunt.utils.normalizelf(options.separator)) : '');
    });

    grunt.registerTask('qunit-node', 'Run QUnit unit tests in a NodeJS instance.', function () {
        var nodequnit = require('qunit')
        ,   done = this.async()
        ,   testFiles = grunt.config()['qunit-node'].testFiles
        ,   tests = [];

        // Filter tests down if given args
        if (this.args.length > 0) {
            grunt.log.writeln('Filtering to ' + this.args);
            this.args.forEach(function (test) {
                if (testFiles.indexOf(test) !== -1) {
                    tests.push(test);
                }
            });
        } else {
            tests = testFiles;
        }

        // Node-nodeQunit shouldnt log stuff
        nodequnit.log.stats = nodequnit.log.reset = nodequnit.log.print = function () {};
        nodequnit.options = {
            assertions: false,
            errors: false,
            tests: false,
            summary: false,
            globalSummary: false,
            coverage: false,
            deps: null,
            namespace: null
        };

        // We'll collect up all of the failed assertions
        var failures = [];
        nodequnit.log.assertion = function (o) {
            if (!o.result) {
                failures.push(o);
            }
        };

        // Grab the final stats
        var stats = {};
        nodequnit.log.summary = function (o) {
            stats = o;
        };

        // Marker for tests
        nodequnit.log.test = function (result) {
            grunt.log.write(+result.failed > 0 ? 'F'.red : '.');
        };

        // Run the tests
        nodequnit.run({
            code: { path: './test/node.test.bootstrap.js', namespace: 'Tempus' },
            tests: tests.map(function (f) { return './test/tempus.test.' + f + '.js'; })
        }, function () {
            // Log out all failures.
            grunt.log.writeln();
            failures.forEach(function (failure) {
                grunt.log.error((failure.module ? failure.module + ' - ' : '') + failure.test);
                if (failure.message) {
                    grunt.log.error('Message: ' + String(failure.message).magenta);
                }
                if (failure.actual !== failure.expected) {
                    grunt.log.error('Actual: ' + String(failure.actual).magenta);
                    grunt.log.error('Expected: ' + String(failure.expected).magenta);
                }
                if (failure.source) {
                    grunt.log.error(failure.source.replace(/ {4}(at)/g, '  $1'));
                }
                grunt.log.writeln();
            });
            if (failures.length) {
                grunt.warn(stats.failed + '/' + stats.total + ' assertions failed (' +
                    stats.runtime + 'ms)', Math.min(99, 90 + stats.failed));
            } else {
                grunt.log.writeln();
                grunt.log.ok(stats.total + ' assertions passed (' + stats.runtime + 'ms)');
            }
            done();
        });
        grunt.log.writeln();
    });

    grunt.registerTask('default', 'concat lint min qunit qunit-node');
};<commit_msg>Fix terrible spelling in grunt.js<commit_after>/*jslint laxcomma: true, loopfunc: true */
module.exports = function (grunt) {

    grunt.initConfig({
        // We want to generate 3 new files, which are part of the release build...
        concat: {
            // With timer, which is Tempus and Tempus.Timer in one export
            with_timer: {
                src: [
                    'tempus.js'
                ,   'tempus.timer.js'
                ],
                dest: './tempus.with.timer.js'
            },
            // With interval, which is Tempus and Tempus.Interval in one export
            with_interval: {
                src: [
                    'tempus.js'
                ,   'tempus.interval.js'
                ],
                dest: './tempus.with.interval.js'
            },
            // With timer and interval; both modules inside one export with Tempus
            with_timer_and_interval: {
                src: [
                    'tempus.js'
                ,   'tempus.interval.js'
                ,   'tempus.timer.js'
                ],
                dest: './tempus.with.timer.and.interval.js'
            }
        },
        // We want the 3 main JS files to be linted, and the concatenated ones
        lint: {
            all: [
                'tempus.js'
            ,   'tempus.interval.js'
            ,   'tempus.timer.js'
            ],
            concatenated: [
                'tempus.with.timer.js'
            ,   'tempus.with.interval.js'
            ,   'tempus.with.interval.and.timer.js'
            ]
        },
        min: {
            // Minify Tempus JS, and also specify a target size when gzipped
            tempus: {
                src: ['tempus.js'],
                dest: 'tempus.min.js',
                size_target: 4505
            },
            // Minify Tempus.Timer
            tempus_timer: {
                src: ['tempus.timer.js'],
                dest: 'tempus.timer.min.js'
            },
            // Minify Tempus.Interval
            tempus_interval: {
                src: ['tempus.interval.js'],
                dest: 'tempus.interval.min.js'
            },
            // Minify our concatenated tempus.with.timer
            tempus_with_timer: {
                src: ['tempus.with.timer.js'],
                dest: 'tempus.with.timer.min.js'
            },
            // Minify our concatenated tempus.with.interval
            tempus_with_interval: {
                src: ['tempus.with.interval.js'],
                dest: 'tempus.with.interval.min.js'
            },
            // Minify our concatenated tempus.with.timer.and.interval
            // with a target size of 7k
            tempus_with_timer_and_interval: {
                src: ['tempus.with.timer.and.interval.js'],
                dest: 'tempus.with.timer.and.interval.min.js',
                size_target: 7168
            }
        },
        // We want to make sure that UglifyJS compresses as much as it can
        uglify: {
            mangle: { top_level: true }
        },
        // We can load in the QUnit test files for phantom.
        qunit: {
            index: ['test/index.html']
        },
        // We'll load in the specific files Node-QUnit will test:
        'qunit-node': {
            testFiles:
                [   'utils'
                ,   'creation'
                ,   'originalMethods'
                ,   'dateMethods'
                ,   'utilityMethods'
                ,   'yearMethods'
                ,   'monthMethods'
                ,   'weekMethods'
                ,   'dayMethods'
                ,   'timeMethods'
                ,   'timezoneMethods'
                ,   'utcMethods'
                ,   'stringMethods'
                ,   'highLevelFunctionality'
                ,   'coremethods'
                ,   'timer'
                ]
        }
    });

    /*******************************************************/
    /************************ TASKS ************************/
    /*******************************************************/

    // We want to overload the min_max_info helper, which is log output
    // minify task. We're overloading it to work with the size_target
    // declaration in the initConfig
    grunt.registerHelper('min_max_info', function(min, max) {
        var gzipSize     = String(grunt.helper('gzip', min).length)
        ,   expectedSize = grunt.task.current.data.size_target || false;

        grunt.log.writeln('Uncompressed size: ' + String(max.length).green + ' bytes.');

        // If size_target doesn't exist, do what min_max_info normally does.
        if (!expectedSize) {
            grunt.log.writeln('Compressed size: ' + gzipSize.green + ' bytes gzipped '+
                '(' + String(min.length).green + ' bytes minified).');

            return;
        }

        // Get the difference in bytes between the two sizes
        var diff = Math.abs(expectedSize - gzipSize);

        // If we're over, flash up a RED size and say we're over
        if (gzipSize > expectedSize) {
            grunt.log.error('Compressed size: ' + gzipSize.red + ' bytes ' +
                '(' + String(diff).red + ' bytes over ' + expectedSize + ' byte target) gzipped ' +
                '(' + String(min.length).green + ' bytes minified).');

        // If we're under, flash up a GREEN size and say we're under
        } else {
            grunt.log.writeln('Compressed size: ' + gzipSize.green + ' bytes ' +
                '(' + String(diff).green + ' bytes under ' + expectedSize + ' byte target) gzipped ' +
                '(' + String(min.length).green + ' bytes minified).');
        }
    });

    // We want to overload the concat helper, which is the meat of the concat task.
    // We're overloading it to strip down the closures of the modules and to insert
    // them into the core's closure, for a better, smaller codebase
    grunt.registerHelper('concat', function(files, options) {
        options = grunt.utils._.defaults(options || {}, {
            separator: grunt.utils.linefeed
        });

        // Get the first file to inject all subsequent files into.
        parent_file = grunt.task.directive(files.shift(), grunt.file.read);

        // Replace the bit which says "// <% MODULE_INJECTION %> //" in the first file, with
        // the stripped down contents of all remaining files....
        return parent_file.replace(/\s+\/\/ <% MODULE_INJECTION %> \/\//, files ? files.map(function(f) {
            var file = grunt.task.directive(f, grunt.file.read);

            // Find the closure function declaration ...
            var match = file.match(/\(function \(.*\n/);
            if (match) {
                // ... and remove everything before it ...
                file = file.substr(match.index)
                    // ... plus the declaration itself, and the next line,
                    // and the last line.
                    .split("\n").slice(2, -1).join("\n");
            }
            // Add a new line plus file comment to the top for formatting
            return "\n\n\t// " + f + " injected\n" + file + "\n\t// End " + f + "\n";
        }).join(grunt.utils.normalizelf(options.separator)) : '');
    });

    grunt.registerTask('qunit-node', 'Run QUnit unit tests in a NodeJS instance.', function () {
        var nodequnit = require('qunit')
        ,   done = this.async()
        ,   testFiles = grunt.config()['qunit-node'].testFiles
        ,   tests = [];

        // Filter tests down if given args
        if (this.args.length > 0) {
            grunt.log.writeln('Filtering to ' + this.args);
            this.args.forEach(function (test) {
                if (testFiles.indexOf(test) !== -1) {
                    tests.push(test);
                }
            });
        } else {
            tests = testFiles;
        }

        // Node-Qunit shouldn’t log stuff
        nodequnit.log.stats = nodequnit.log.reset = nodequnit.log.print = function () {};
        nodequnit.options = {
            assertions: false,
            errors: false,
            tests: false,
            summary: false,
            globalSummary: false,
            coverage: false,
            deps: null,
            namespace: null
        };

        // We'll collect up all of the failed assertions
        var failures = [];
        nodequnit.log.assertion = function (o) {
            if (!o.result) {
                failures.push(o);
            }
        };

        // Grab the final stats
        var stats = {};
        nodequnit.log.summary = function (o) {
            stats = o;
        };

        // Marker for tests
        nodequnit.log.test = function (result) {
            grunt.log.write(+result.failed > 0 ? 'F'.red : '.');
        };

        // Run the tests
        nodequnit.run({
            code: { path: './test/node.test.bootstrap.js', namespace: 'Tempus' },
            tests: tests.map(function (f) { return './test/tempus.test.' + f + '.js'; })
        }, function () {
            // Log out all failures.
            grunt.log.writeln();
            failures.forEach(function (failure) {
                grunt.log.error((failure.module ? failure.module + ' - ' : '') + failure.test);
                if (failure.message) {
                    grunt.log.error('Message: ' + String(failure.message).magenta);
                }
                if (failure.actual !== failure.expected) {
                    grunt.log.error('Actual: ' + String(failure.actual).magenta);
                    grunt.log.error('Expected: ' + String(failure.expected).magenta);
                }
                if (failure.source) {
                    grunt.log.error(failure.source.replace(/ {4}(at)/g, '  $1'));
                }
                grunt.log.writeln();
            });
            if (failures.length) {
                grunt.warn(stats.failed + '/' + stats.total + ' assertions failed (' +
                    stats.runtime + 'ms)', Math.min(99, 90 + stats.failed));
            } else {
                grunt.log.writeln();
                grunt.log.ok(stats.total + ' assertions passed (' + stats.runtime + 'ms)');
            }
            done();
        });
        grunt.log.writeln();
    });

    grunt.registerTask('default', 'concat lint min qunit qunit-node');
};<|endoftext|>


<commit_before>// Load in test dependencies
var lineHeight = require('../lib/line-height.js'),
    assert = require('proclaim'),
    domify = require('domify'),
    cssControls = require('css-controls');

// TODO: Review all tests and make sure they have clear intent. If not, comment.

// Create common fixture actions
function fixtureNode() {
  before(function () {
    var node = domify(this.html);
    document.body.appendChild(node);
    this.node = node;
  });
  after(function () {
    document.body.removeChild(this.node);
  });
}

function processNode() {
  before(function () {
    this.lineHeight = lineHeight(this.node);
  });

  it('has a line-height which is a number', function () {
    assert.strictEqual(typeof this.lineHeight, 'number');
    assert.notEqual(isNaN(this.lineHeight), true);
  });
}

function styleBody(css) {
  before(function () {
    document.body.style.cssText = css;
  });
  after(function () {
    document.body.style.cssText = '';
  });
}

var styleSheet = cssControls.createStyleSheet();
function globalStyle(selector, rule) {
  var index;
  before(function () {
    index = cssControls.addRule(styleSheet, selector, rule);
  });
  after(function () {
    cssControls.removeRule(styleSheet, index);
  });
}

// Basic tests
var _defaultLnHeight;
describe('An unstyled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    before(function () {
      // Save the line height for other tests
      _defaultLnHeight = this.lineHeight;
    });

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });
  });
});

describe('A line-height styled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has the styled line-height\'s height', function () {
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

// DEV: Tests and disproves that an element has a constant ratio for its font-size
describe('A font-size styled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has the styled line-height\'s height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the its font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > 50, 'Expected: > 50, Actual: ' + lnHeight);
    });
  });
});

// Intermediate tests
describe('A percentage line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 150% !== default * 1.5; 24 !== 28.5 (19 * 1.5)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

describe('A relative line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 1.3em !== default * 1.3; 22 !== 24.7 (19 * 1.3)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// DEV: This is redundant but the test name is practical
describe('An absolute line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to 50px', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

describe('A numeric line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 2.3 !== default * 2.3; 37 !== 43.6999... (19 * 2.3)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// Verify ancestor -> descendant works on global styling to node level
describe('An inherit line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  styleBody('line-height: 40px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to the inherited amount', function () {
      assert.strictEqual(this.lineHeight, 40);
    });
  });
});

// Verify ancestor -> descendant works on node to node level
describe('A child in a styled div', function () {
  before(function () {
    this.html = '

abc

';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    before(function () {
      var childNode = document.getElementById('child');
      this.lineHeight = lineHeight(childNode);
    });

    it('has a line-height equal to the parent\'s line-height', function () {
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

// Advanced tests
// Verify more global styling inheritance
describe('A globally styled body and an unstyled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  styleBody('font-size: 40px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the body\'s font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > 40, 'Expected: > 40, Actual: ' + lnHeight);
    });
  });
});

// Kitchen sink tests
// Testing a pt unit type explicitly
describe('A pt line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 36px', function () {
      assert.strictEqual(this.lineHeight, 27 * 4/3);
    });
  });
});

// Testing a mm unit type explicitly (IE6)
describe('A mm line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 192px', function () {
      assert.strictEqual(this.lineHeight, 192); // 50.8 * 96/25.4
    });
  });
});

// Testing a cm unit type explicitly (IE6)
describe('A cm line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 96px', function () {
      assert.strictEqual(this.lineHeight, 96); // 2.54 * 96/2.54
    });
  });
});

// Testing a in unit type explicitly (IE6)
describe('A in line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 192px', function () {
      assert.strictEqual(this.lineHeight, 192); // 2 * 96
    });
  });
});

// Testing a pc unit type explicitly (IE6)
describe('A pc line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 32px', function () {
      assert.strictEqual(this.lineHeight, 32); // 2pc * 12pt/1pc * 4px/3pt
    });
  });
});

// Mass test all other unit types
// DEV: Units taken from https://developer.mozilla.org/en-US/docs/Web/CSS/length
var cssLengths = ['em', 'ex', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax', 'px', 'mm', 'cm', 'in', 'pt', 'pc', 'mozmm'],
    i = cssLengths.length;
function testCssLength(cssLength) {
  describe('A ' + cssLength + ' line-height div', function () {
    before(function () {
      this.html = '
abc
';
    });
    fixtureNode();

    describe('processed by line-height', function () {
      processNode();

      it('has a line-height equal to its height', function () {
        var height = this.node.offsetHeight;
        assert.strictEqual(this.lineHeight, height);
      });
    });
  });
}
while (i--) {
  testCssLength(cssLengths[i]);
}

// Verify there is no bleeding between
describe('An em line-height with a pt font div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// Verify we return a line-height specific for a the tag type (e.g. h2 over div)
describe('A div-specific font-size style and an h2', function () {
  before(function () {
    this.html = '
abc
';
  });
  globalStyle('div', 'font-size: 60px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height under the div font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight < 60, 'Expected: < 60, Actual: ' + lnHeight);
    });
  });
});
<commit_msg>Fixing iOS being silly with 1.3em<commit_after>// Load in test dependencies
var lineHeight = require('../lib/line-height.js'),
    assert = require('proclaim'),
    domify = require('domify'),
    cssControls = require('css-controls');

// TODO: Review all tests and make sure they have clear intent. If not, comment.

// Create common fixture actions
function fixtureNode() {
  before(function () {
    var node = domify(this.html);
    document.body.appendChild(node);
    this.node = node;
  });
  after(function () {
    document.body.removeChild(this.node);
  });
}

function processNode() {
  before(function () {
    this.lineHeight = lineHeight(this.node);
  });

  it('has a line-height which is a number', function () {
    assert.strictEqual(typeof this.lineHeight, 'number');
    assert.notEqual(isNaN(this.lineHeight), true);
  });
}

function styleBody(css) {
  before(function () {
    document.body.style.cssText = css;
  });
  after(function () {
    document.body.style.cssText = '';
  });
}

var styleSheet = cssControls.createStyleSheet();
function globalStyle(selector, rule) {
  var index;
  before(function () {
    index = cssControls.addRule(styleSheet, selector, rule);
  });
  after(function () {
    cssControls.removeRule(styleSheet, index);
  });
}

// Basic tests
var _defaultLnHeight;
describe('An unstyled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    before(function () {
      // Save the line height for other tests
      _defaultLnHeight = this.lineHeight;
    });

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });
  });
});

describe('A line-height styled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has the styled line-height\'s height', function () {
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

// DEV: Tests and disproves that an element has a constant ratio for its font-size
describe('A font-size styled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has the styled line-height\'s height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the its font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > 50, 'Expected: > 50, Actual: ' + lnHeight);
    });
  });
});

// Intermediate tests
describe('A percentage line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 150% !== default * 1.5; 24 !== 28.5 (19 * 1.5)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

describe('A relative line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 1.3em !== default * 1.3; 22 !== 24.7 (19 * 1.3)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// DEV: This is redundant but the test name is practical
describe('An absolute line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to 50px', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

describe('A numeric line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      // DEV: In IE6, 2.3 !== default * 2.3; 37 !== 43.6999... (19 * 2.3)
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// Verify ancestor -> descendant works on global styling to node level
describe('An inherit line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  styleBody('line-height: 40px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to the inherited amount', function () {
      assert.strictEqual(this.lineHeight, 40);
    });
  });
});

// Verify ancestor -> descendant works on node to node level
describe('A child in a styled div', function () {
  before(function () {
    this.html = '

abc

';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    before(function () {
      var childNode = document.getElementById('child');
      this.lineHeight = lineHeight(childNode);
    });

    it('has a line-height equal to the parent\'s line-height', function () {
      assert.strictEqual(this.lineHeight, 50);
    });
  });
});

// Advanced tests
// Verify more global styling inheritance
describe('A globally styled body and an unstyled div', function () {
  before(function () {
    this.html = '
abc
';
  });
  styleBody('font-size: 40px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the body\'s font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > 40, 'Expected: > 40, Actual: ' + lnHeight);
    });
  });
});

// Kitchen sink tests
// Testing a pt unit type explicitly
describe('A pt line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 36px', function () {
      assert.strictEqual(this.lineHeight, 27 * 4/3);
    });
  });
});

// Testing a mm unit type explicitly (IE6)
describe('A mm line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 192px', function () {
      assert.strictEqual(this.lineHeight, 192); // 50.8 * 96/25.4
    });
  });
});

// Testing a cm unit type explicitly (IE6)
describe('A cm line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 96px', function () {
      assert.strictEqual(this.lineHeight, 96); // 2.54 * 96/2.54
    });
  });
});

// Testing a in unit type explicitly (IE6)
describe('A in line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 192px', function () {
      assert.strictEqual(this.lineHeight, 192); // 2 * 96
    });
  });
});

// Testing a pc unit type explicitly (IE6)
describe('A pc line-height div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    // DEV: This verifies our conversion is correct
    it('has a line-height of 32px', function () {
      assert.strictEqual(this.lineHeight, 32); // 2pc * 12pt/1pc * 4px/3pt
    });
  });
});

// Mass test all other unit types
// DEV: Units taken from https://developer.mozilla.org/en-US/docs/Web/CSS/length
var cssLengths = ['em', 'ex', 'ch', 'rem', 'vh', 'vw', 'vmin', 'vmax', 'px', 'mm', 'cm', 'in', 'pt', 'pc', 'mozmm'],
    i = cssLengths.length;
function testCssLength(cssLength) {
  describe('A ' + cssLength + ' line-height div', function () {
    before(function () {
      this.html = '
abc
';
    });
    fixtureNode();

    describe('processed by line-height', function () {
      processNode();

      it('has a line-height equal to its height', function () {
        var height = this.node.offsetHeight;
        assert.strictEqual(this.lineHeight, height);
      });
    });
  });
}
while (i--) {
  testCssLength(cssLengths[i]);
}

// Verify there is no bleeding between
describe('An em line-height with a pt font div', function () {
  before(function () {
    this.html = '
abc
';
  });
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height greater than the default', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight > _defaultLnHeight, 'Expected: > ' + _defaultLnHeight + ' (default), Actual: ' + lnHeight);
    });
  });
});

// Verify we return a line-height specific for a the tag type (e.g. h2 over div)
describe('A div-specific font-size style and an h2', function () {
  before(function () {
    this.html = '
abc
';
  });
  globalStyle('div', 'font-size: 60px;');
  fixtureNode();

  describe('processed by line-height', function () {
    processNode();

    it('has a line-height equal to its height', function () {
      var height = this.node.offsetHeight;
      assert.strictEqual(this.lineHeight, height);
    });

    it('has a line-height under the div font-size', function () {
      var lnHeight = this.lineHeight;
      assert.ok(lnHeight < 60, 'Expected: < 60, Actual: ' + lnHeight);
    });
  });
});
<|endoftext|>


<commit_before>var partial = require('chessground').util.partial;
var router = require('game').router;
var util = require('./util');
var pgnExport = require('./pgnExport');
var m = require('mithril');

var baseSpeeds = [{
  name: 'fast',
  delay: 1000
}, {
  name: 'slow',
  delay: 5000
}];

var allSpeeds = baseSpeeds.concat({
  name: 'realtime',
  delay: 'realtime'
});

var cplSpeeds = [{
  name: 'by CPL',
  delay: 'cpl_slow'
}];

function deleteButton(data, userId) {
  if (data.game.source === 'import' &&
    data.game.importedBy && data.game.importedBy === userId)
    return m('form.delete', {
      method: 'post',
      action: '/' + data.game.id + '/delete',
      onsubmit: function() {
        return confirm('Delete this imported game?');
      }
    }, m('button.button.text.thin', {
      type: 'submit',
      'data-icon': 'q',
    }, 'Delete'));
}

function autoplayButtons(ctrl) {
  var d = ctrl.data;
  var speeds = d.game.moveTimes.length ? allSpeeds : baseSpeeds;
  speeds = d.analysis ? speeds.concat(cplSpeeds) : speeds;
  return m('div.autoplay', speeds.map(function(speed, i) {
    return m('a', {
      class: 'fbt' + (ctrl.autoplay.active(speed.delay) ? ' active' : ''),
      config: util.bindOnce('click', partial(ctrl.togglePlay, speed.delay))
    }, speed.name);
  }));
}

function rangeConfig(read, write) {
  return function(el, isUpdate, ctx) {
    if (isUpdate) return;
    el.value = read();
    var handler = function(e) {
      write(e.target.value);
    };
    var blurer = function(e) {
      e.target.blur();
    };
    el.addEventListener('input', handler)
    el.addEventListener('mouseout', blurer)
    ctx.onunload = function() {
      el.removeEventListener('input', handler);
      el.removeEventListener('mouseout', blurer);
    };
  };
}

function formatHashSize(v) {
  if (v < 1000) return v + 'MB';
  else return Math.round(v / 1024) + 'GB';
}

function studyButton(ctrl) {
  if (ctrl.study || ctrl.ongoing) return;
  var realGame = !util.synthetic(ctrl.data);
  return m('form', {
    method: 'post',
    action: '/study',
    onsubmit: function(e) {
      var pgnInput = e.target.querySelector('input[name=pgn]');
      if (pgnInput) pgnInput.value = pgnExport.renderFullTxt(ctrl);
    }
  }, [
    realGame ? m('input[type=hidden][name=gameId]', {
      value: ctrl.data.game.id
    }) : m('input[type=hidden][name=pgn]'),
    m('input[type=hidden][name=orientation]', {
      value: ctrl.chessground.data.orientation
    }),
    m('input[type=hidden][name=variant]', {
      value: ctrl.data.game.variant.key
    }),
    m('input[type=hidden][name=fen]', {
      value: ctrl.tree.root.fen
    }),
    m('button.fbt', {
        type: 'submit'
      },
      m('i.icon', {
        'data-icon': ''
      }),
      realGame ? 'Create Study' : 'Save to Study')
  ]);
}

module.exports = {
  controller: function() {

    this.open = location.hash === '#menu';

    this.toggle = function() {
      this.open = !this.open;
    }.bind(this);
  },
  view: function(ctrl) {
    var flipAttrs = {};
    var d = ctrl.data;
    if (d.userAnalysis) flipAttrs.config = util.bindOnce('click', ctrl.flip);
    else flipAttrs.href = router.game(d, d.opponent.color) + '#' + ctrl.vm.node.ply;
    var canContinue = !ctrl.ongoing && d.game.variant.key === 'standard';

    return m('div.action_menu', [
      m('h2', 'Computer analysis'), [
        (function(id) {
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Best move arrow'),
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: ctrl.vm.showAutoShapes(),
                config: util.bindOnce('change', function(e) {
                  ctrl.toggleAutoShapes(e.target.checked);
                })
              }),
              m('label', {
                'for': id
              })
            ])
          ]);
        })('analyse-toggle-ceval'), (function(id) {
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Evaluation gauge'),
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: ctrl.vm.showGauge(),
                config: util.bindOnce('change', function(e) {
                  ctrl.toggleGauge(e.target.checked);
                })
              }),
              m('label', {
                'for': id
              })
            ])
          ]);
        })('analyse-toggle-gauge'), (function(id) {
          var max = 5;
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Multiple lines'),
            m('input', {
              id: id,
              type: 'range',
              min: 1,
              max: max,
              step: 1,
              config: rangeConfig(function() {
                return ctrl.ceval.multiPv();
              }, function(v) {
                ctrl.cevalSetMultiPv(parseInt(v));
              })
            }),
            m('div.range_value', ctrl.ceval.multiPv() + ' / ' + max)
          ]);
        })('analyse-multipv'),
        ctrl.ceval.pnaclSupported ? [
          (function(id) {
            var max = navigator.hardwareConcurrency || 1;
            return m('div.setting', [
              m('label', {
                'for': id
              }, 'Threads'),
              m('input', {
                id: id,
                type: 'range',
                min: 1,
                max: max,
                step: 1,
                config: rangeConfig(function() {
                  return ctrl.ceval.threads();
                }, function(v) {
                  ctrl.cevalSetThreads(parseInt(v));
                })
              }),
              m('div.range_value', ctrl.ceval.threads() + ' / ' + max)
            ]);
          })('analyse-threads'), (function(id) {
            return m('div.setting', [
              m('label', {
                'for': id
              }, 'Memory'),
              m('input', {
                id: id,
                type: 'range',
                min: 4,
                max: 10,
                step: 1,
                config: rangeConfig(function() {
                  return Math.floor(Math.log2(ctrl.ceval.hashSize()));
                }, function(v) {
                  ctrl.cevalSetHashSize(Math.pow(2, parseInt(v)));
                })
              }),
              m('div.range_value', formatHashSize(ctrl.ceval.hashSize()))
            ]);
          })('analyse-memory')
        ] : null
      ],
      m('h2', 'Tools'),
      m('div.tools', [
        m('a.fbt', flipAttrs, m('i.icon[data-icon=B]'), ctrl.trans('flipBoard')),
        ctrl.ongoing ? null : m('a.fbt', {
          href: d.userAnalysis ? '/editor?fen=' + ctrl.vm.node.fen : '/' + d.game.id + '/edit?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, [
          m('i.icon[data-icon=m]'),
          ctrl.trans('boardEditor')
        ]),
        canContinue ? m('a.fbt', {
          config: util.bindOnce('click', function() {
            $.modal($('.continue_with.' + d.game.id));
          })
        }, [
          m('i.icon[data-icon=U]'),
          ctrl.trans('continueFromHere')
        ]) : null,
        studyButton(ctrl)
      ]),
      ctrl.vm.mainline.length > 4 ? [m('h2', 'Replay mode'), autoplayButtons(ctrl)] : null,
      deleteButton(d, ctrl.userId),
      ctrl.ongoing ? null : m('div.continue_with.' + d.game.id, [
        m('a.button', {
          href: d.userAnalysis ? '/?fen=' + ctrl.encodeNodeFen() + '#ai' : router.continue(d, 'ai') + '?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, ctrl.trans('playWithTheMachine')),
        m('br'),
        m('a.button', {
          href: d.userAnalysis ? '/?fen=' + ctrl.encodeNodeFen() + '#friend' : router.continue(d, 'friend') + '?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, ctrl.trans('playWithAFriend'))
      ])
    ]);
  }
};
<commit_msg>maybe more familiar wording<commit_after>var partial = require('chessground').util.partial;
var router = require('game').router;
var util = require('./util');
var pgnExport = require('./pgnExport');
var m = require('mithril');

var baseSpeeds = [{
  name: 'fast',
  delay: 1000
}, {
  name: 'slow',
  delay: 5000
}];

var allSpeeds = baseSpeeds.concat({
  name: 'realtime',
  delay: 'realtime'
});

var cplSpeeds = [{
  name: 'by CPL',
  delay: 'cpl_slow'
}];

function deleteButton(data, userId) {
  if (data.game.source === 'import' &&
    data.game.importedBy && data.game.importedBy === userId)
    return m('form.delete', {
      method: 'post',
      action: '/' + data.game.id + '/delete',
      onsubmit: function() {
        return confirm('Delete this imported game?');
      }
    }, m('button.button.text.thin', {
      type: 'submit',
      'data-icon': 'q',
    }, 'Delete'));
}

function autoplayButtons(ctrl) {
  var d = ctrl.data;
  var speeds = d.game.moveTimes.length ? allSpeeds : baseSpeeds;
  speeds = d.analysis ? speeds.concat(cplSpeeds) : speeds;
  return m('div.autoplay', speeds.map(function(speed, i) {
    return m('a', {
      class: 'fbt' + (ctrl.autoplay.active(speed.delay) ? ' active' : ''),
      config: util.bindOnce('click', partial(ctrl.togglePlay, speed.delay))
    }, speed.name);
  }));
}

function rangeConfig(read, write) {
  return function(el, isUpdate, ctx) {
    if (isUpdate) return;
    el.value = read();
    var handler = function(e) {
      write(e.target.value);
    };
    var blurer = function(e) {
      e.target.blur();
    };
    el.addEventListener('input', handler)
    el.addEventListener('mouseout', blurer)
    ctx.onunload = function() {
      el.removeEventListener('input', handler);
      el.removeEventListener('mouseout', blurer);
    };
  };
}

function formatHashSize(v) {
  if (v < 1000) return v + 'MB';
  else return Math.round(v / 1024) + 'GB';
}

function studyButton(ctrl) {
  if (ctrl.study || ctrl.ongoing) return;
  var realGame = !util.synthetic(ctrl.data);
  return m('form', {
    method: 'post',
    action: '/study',
    onsubmit: function(e) {
      var pgnInput = e.target.querySelector('input[name=pgn]');
      if (pgnInput) pgnInput.value = pgnExport.renderFullTxt(ctrl);
    }
  }, [
    realGame ? m('input[type=hidden][name=gameId]', {
      value: ctrl.data.game.id
    }) : m('input[type=hidden][name=pgn]'),
    m('input[type=hidden][name=orientation]', {
      value: ctrl.chessground.data.orientation
    }),
    m('input[type=hidden][name=variant]', {
      value: ctrl.data.game.variant.key
    }),
    m('input[type=hidden][name=fen]', {
      value: ctrl.tree.root.fen
    }),
    m('button.fbt', {
        type: 'submit'
      },
      m('i.icon', {
        'data-icon': ''
      }),
      realGame ? 'Create Study' : 'Save to Study')
  ]);
}

module.exports = {
  controller: function() {

    this.open = location.hash === '#menu';

    this.toggle = function() {
      this.open = !this.open;
    }.bind(this);
  },
  view: function(ctrl) {
    var flipAttrs = {};
    var d = ctrl.data;
    if (d.userAnalysis) flipAttrs.config = util.bindOnce('click', ctrl.flip);
    else flipAttrs.href = router.game(d, d.opponent.color) + '#' + ctrl.vm.node.ply;
    var canContinue = !ctrl.ongoing && d.game.variant.key === 'standard';

    return m('div.action_menu', [
      m('h2', 'Computer analysis'), [
        (function(id) {
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Best move arrow'),
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: ctrl.vm.showAutoShapes(),
                config: util.bindOnce('change', function(e) {
                  ctrl.toggleAutoShapes(e.target.checked);
                })
              }),
              m('label', {
                'for': id
              })
            ])
          ]);
        })('analyse-toggle-ceval'), (function(id) {
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Evaluation gauge'),
            m('div.switch', [
              m('input', {
                id: id,
                class: 'cmn-toggle cmn-toggle-round',
                type: 'checkbox',
                checked: ctrl.vm.showGauge(),
                config: util.bindOnce('change', function(e) {
                  ctrl.toggleGauge(e.target.checked);
                })
              }),
              m('label', {
                'for': id
              })
            ])
          ]);
        })('analyse-toggle-gauge'), (function(id) {
          var max = 5;
          return m('div.setting', [
            m('label', {
              'for': id
            }, 'Multiple lines'),
            m('input', {
              id: id,
              type: 'range',
              min: 1,
              max: max,
              step: 1,
              config: rangeConfig(function() {
                return ctrl.ceval.multiPv();
              }, function(v) {
                ctrl.cevalSetMultiPv(parseInt(v));
              })
            }),
            m('div.range_value', ctrl.ceval.multiPv() + ' / ' + max)
          ]);
        })('analyse-multipv'),
        ctrl.ceval.pnaclSupported ? [
          (function(id) {
            var max = navigator.hardwareConcurrency || 1;
            return m('div.setting', [
              m('label', {
                'for': id
              }, 'CPUs'),
              m('input', {
                id: id,
                type: 'range',
                min: 1,
                max: max,
                step: 1,
                config: rangeConfig(function() {
                  return ctrl.ceval.threads();
                }, function(v) {
                  ctrl.cevalSetThreads(parseInt(v));
                })
              }),
              m('div.range_value', ctrl.ceval.threads() + ' / ' + max)
            ]);
          })('analyse-threads'), (function(id) {
            return m('div.setting', [
              m('label', {
                'for': id
              }, 'Memory'),
              m('input', {
                id: id,
                type: 'range',
                min: 4,
                max: 10,
                step: 1,
                config: rangeConfig(function() {
                  return Math.floor(Math.log2(ctrl.ceval.hashSize()));
                }, function(v) {
                  ctrl.cevalSetHashSize(Math.pow(2, parseInt(v)));
                })
              }),
              m('div.range_value', formatHashSize(ctrl.ceval.hashSize()))
            ]);
          })('analyse-memory')
        ] : null
      ],
      m('h2', 'Tools'),
      m('div.tools', [
        m('a.fbt', flipAttrs, m('i.icon[data-icon=B]'), ctrl.trans('flipBoard')),
        ctrl.ongoing ? null : m('a.fbt', {
          href: d.userAnalysis ? '/editor?fen=' + ctrl.vm.node.fen : '/' + d.game.id + '/edit?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, [
          m('i.icon[data-icon=m]'),
          ctrl.trans('boardEditor')
        ]),
        canContinue ? m('a.fbt', {
          config: util.bindOnce('click', function() {
            $.modal($('.continue_with.' + d.game.id));
          })
        }, [
          m('i.icon[data-icon=U]'),
          ctrl.trans('continueFromHere')
        ]) : null,
        studyButton(ctrl)
      ]),
      ctrl.vm.mainline.length > 4 ? [m('h2', 'Replay mode'), autoplayButtons(ctrl)] : null,
      deleteButton(d, ctrl.userId),
      ctrl.ongoing ? null : m('div.continue_with.' + d.game.id, [
        m('a.button', {
          href: d.userAnalysis ? '/?fen=' + ctrl.encodeNodeFen() + '#ai' : router.continue(d, 'ai') + '?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, ctrl.trans('playWithTheMachine')),
        m('br'),
        m('a.button', {
          href: d.userAnalysis ? '/?fen=' + ctrl.encodeNodeFen() + '#friend' : router.continue(d, 'friend') + '?fen=' + ctrl.vm.node.fen,
          rel: 'nofollow'
        }, ctrl.trans('playWithAFriend'))
      ])
    ]);
  }
};
<|endoftext|>


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

'use strict';

var router = require('express').Router(),
  mongoose = require('mongoose'),
  fs       = require('fs'),
  Q        = require('q'),
  Profile  = mongoose.model('Profile'),
  User     = mongoose.model('User');

/**
 * Render the celebrity list
*/
router.get('/', function(req,res) {
  Profile.find({},function(err,profiles){
    if (err)
      res.render('celebrities',{error: err});
    else
      res.render('celebrities',{profiles: profiles});
  });
});

/**
 * Render the celebrity list
*/
router.get('/users', function(req,res) {
  User.find({},function(err,profiles){
    if (err)
      res.render('celebrities',{error: err});
    else
      res.render('celebrities',{profiles: profiles});
  });
});

var getUserId = function(text) {
  return text.replace('.json','');
};

var jsonProfiles = function(text) {
  return text.indexOf('.json', text.length - '.json'.length) !== -1;
};

/**
 * Validate twitter usernames
*/
router.post('/syncdb', function (req, res) {
  console.log('update celebrity database');
  var removeAll = Q.denodeify(Profile.remove.bind(Profile)),
    getFiles = Q.denodeify(fs.readdir),
    getUsers = Q.denodeify(req.twit.getUsers.bind(req.twit)),
    getFile = Q.denodeify(fs.readFile);

  var newUsers;
  removeAll({})
  .then(function() {
    return getFiles('./profiles');
  })
  .then(function(files){
    if (!files || files.length === 0)
      return;

    var user_ids = files.filter(jsonProfiles).map(getUserId),
      count = Math.ceil(user_ids.length / 100),
      promises = [];

    for (var i = 0; i < count; i++) {
      var ids = user_ids.slice(0,100);
      user_ids = user_ids.slice(Math.min(100, user_ids.length));
      promises.push(getUsers({user_id:ids.join(',')}));
    }
    return Q.all(promises);
  })
  .then(function(usersArray){
    var users = [];
    usersArray.forEach(function(_users){
      users = users.concat(_users);
    });
    newUsers = usersArray;

    return Q.all(users.map(function(u){
      getFile('./profiles/'+u.id+'.json')
      .then(function(profileJson) {
        u.profile = profileJson;
        return Profile.create(u).exec();
      });
    }));
  })
  .then(function(){
    res.json({success:true, profiles:newUsers});
  })
  .catch(function (error) {
    console.log('error', error);
    res.json({success:false, error:error});
  });
});

/**
 * Add a celebrity to the list
*/
router.post('/add/@:username', function(req,res) {
  var username = req.params.username;
  console.log(username);
  // Check if the user exists
  Profile.findOne({username:username}, function(err,profile) {
    if (err)
      res.json({success:false, error: err});
    else if (profile) {
      console.log('User is already in the database');
      res.json({success:false, error: 'User is already in the database'});
    }
    else {
      // Check if the user is verified, >10k followers, and >1k tweets
      var showUser = Q.denodeify(req.twit.showUser.bind(req.twit));
      showUser(username)
      .then(function(user) {
        if (!user.verified || user.tweets < 1000 || user.followers < 10000)
          res.json({success:false, error: 'User does not qualify as a celebrity'});
        else if (user.protected)
          res.json({success:false, error: 'User is protected and cannot be added'});
        else {
          // Get the tweets, profile and add him to the database
          var getTweets = Q.denodeify(req.twit.getTweets.bind(req.twit));
          return getTweets(username)
          .then(function(tweets) {
            console.log(username, 'has', tweets.length, 'tweets');
            var getProfile = Q.denodeify(req.personality_insights.profile.bind(req.personality_insights));
            return getProfile({contentItems:tweets})
            .then(function(profile) {
              if (!profile)
                return;
              console.log(username, 'analyze with personality insights');

              console.log(username, 'added to the database');
              user.profile = JSON.stringify(profile);
              var saveProfileInDB = Q.denodeify(Profile.createOrUpdate.bind(Profile));
              return saveProfileInDB(user);
            });
          })
          .then(function(dbUser) {
            if (!dbUser) return;
            res.json({success:true, profile: dbUser});

            // return null because we already fulfill the response
            return null;
          });
        }
      })
      .catch(function (error) {
        console.log('catch():', error);
        var err,
          status = 500;
        if (error.statusCode === 429)
          err = 'Twitter rate limit exceeded, come back in 15 minutes.';
        else if (error.statusCode === 503)
          err = 'The Twitter servers are overloaded with requests. Try again later.';
        else if (error.statusCode === 404) {
          err = 'Sorry, @' + username + ' does not exist.';
          status = 404;
        } else {
          err = 'Sorry, there was an error. Please try again later.';
        }

      res.status(status);
      res.json({success:false, error: err});

      // return null because we already fulfill the response
      return null;
      });
    }
  });
});

/**
 * Render the celebrity list
*/
router.post('/remove/@:username', function(req,res) {
  var username = req.params.username;
  Profile.remove({username:username},function(err, result){
    if (err)
      res.json({success:false, error: err});
    else {
      res.json({success:true, username: username});
    }
  });
});

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

'use strict';

var router = require('express').Router(),
  mongoose = require('mongoose'),
  fs       = require('fs'),
  Q        = require('q'),
  Profile  = mongoose.model('Profile'),
  User     = mongoose.model('User');

/**
 * Render the celebrity list
 */
router.get('/', function(req,res) {
  Profile.find({},function(err,profiles){
    if (err)
      res.render('celebrities',{error: err});
    else
      res.render('celebrities',{profiles: profiles});
  });
});

/**
 * Render the celebrity list
 */
router.get('/users', function(req,res) {
  User.find({},function(err,profiles){
    if (err)
      res.render('celebrities',{error: err});
    else
      res.render('celebrities',{profiles: profiles});
  });
});

var getUserId = function(text) {
  return text.replace('.json','');
};

var jsonProfiles = function(text) {
  return text.indexOf('.json', text.length - '.json'.length) !== -1;
};

/**
 * Validate twitter usernames
 */
router.post('/syncdb', function (req, res) {
  console.log('update celebrity database');
  var removeAll = Q.denodeify(Profile.remove.bind(Profile)),
    getFiles = Q.denodeify(fs.readdir),
    getUsers = Q.denodeify(req.twit.getUsers.bind(req.twit)),
    getFile = Q.denodeify(fs.readFile);

  var newUsers;
  removeAll({})
  .then(function() {
    return getFiles('./profiles');
  })
  .then(function(files){
    if (!files || files.length === 0)
      return;

    var user_ids = files.filter(jsonProfiles).map(getUserId),
      count = Math.ceil(user_ids.length / 100),
      promises = [];

    for (var i = 0; i < count; i++) {
      var ids = user_ids.slice(0,100);
      user_ids = user_ids.slice(Math.min(100, user_ids.length));
      promises.push(getUsers({user_id:ids.join(',')}));
    }
    return Q.all(promises);
  })
  .then(function(usersArray){
    var users = [];
    usersArray.forEach(function(_users){
      users = users.concat(_users);
    });
    newUsers = usersArray;

    return Q.all(users.map(function(u){
      getFile('./profiles/'+u.id+'.json')
      .then(function(profileJson) {
        u.profile = profileJson;
        return Profile.create(u).exec();
      });
    }));
  })
  .then(function(){
    res.json({success:true, profiles:newUsers});
  })
  .catch(function (error) {
    console.log('error', error);
    res.json({success:false, error:error});
  });
});

/**
 * Add a celebrity to the list
 */
router.post('/add/@:username', function(req,res) {
  var username = req.params.username;
  console.log(username);
  // Check if the user exists
  Profile.findOne({username:username}, function(err,profile) {
    if (err)
      res.json({success:false, error: err});
    else if (profile) {
      console.log('User is already in the database');
      res.json({success:false, error: 'User is already in the database'});
    }
    else {
      // Check if the user is verified, >10k followers, and >1k tweets
      var showUser = Q.denodeify(req.twit.showUser.bind(req.twit));
      showUser(username)
      .then(function(user) {
        if (!user.verified || user.tweets < 1000 || user.followers < 10000)
          res.json({success:false, error: 'User does not qualify as a celebrity'});
        else if (user.protected)
          res.json({success:false, error: 'User is protected and cannot be added'});
        else {
          // Get the tweets, profile and add him to the database
          var getTweets = Q.denodeify(req.twit.getTweets.bind(req.twit));
          return getTweets(username)
          .then(function(tweets) {
            console.log(username, 'has', tweets.length, 'tweets');
            var getProfile = Q.denodeify(req.personality_insights.profile.bind(req.personality_insights));
            return getProfile({contentItems:tweets})
            .then(function(profile) {
              if (!profile)
                return;
              console.log(username, 'analyze with personality insights');

              console.log(username, 'added to the database');
              user.profile = JSON.stringify(profile);
              var saveProfileInDB = Q.denodeify(Profile.createOrUpdate.bind(Profile));
              return saveProfileInDB(user);
            });
          })
          .then(function(dbUser) {
            if (!dbUser) return;
            res.json({success:true, profile: dbUser});

            // return null because we already fulfill the response
            return null;
          });
        }
      })
      .catch(function (error) {
        console.log('catch():', error);
        var err,
          status = 500;
        if (error.statusCode === 429)
          err = 'Twitter rate limit exceeded, come back in 15 minutes.';
        else if (error.statusCode === 503)
          err = 'The Twitter servers are overloaded with requests. Try again later.';
        else if (error.statusCode === 404) {
          err = 'Sorry, @' + username + ' does not exist.';
          status = 404;
        } else {
          err = 'Sorry, there was an error. Please try again later.';
        }

      res.status(status);
      res.json({success:false, error: err});

      // return null because we already fulfill the response
      return null;
      });
    }
  });
});

/**
 * Render the celebrity list
 */
router.post('/remove/@:username', function(req,res) {
  var username = req.params.username;
  Profile.remove({username:username},function(err, result){
    if (err)
      res.json({success:false, error: err});
    else {
      res.json({success:true, username: username});
    }
  });
});

module.exports = router;
<|endoftext|>