<commit_before>var rnewlineseek = /(\r\n|\r|\n)/g,
	rwhitespace = /(\s|\t|\r\n|\r|\n)/,
	leftTrim = /^(\r\n|\r|\n|\t|\s)*/;

function CSSTree( css ) {
	var self = this, m;

	// Force instance of CSSTree
	if ( ! ( self instanceof CSSTree ) ) {
		return new CSSTree( css );
	}

	// Internals
	self.css = css || '';
	self.i = -1;
	self.length = self.css.length;
	self.branches = [];

	// Positional setup
	self._newlines = [];
	while ( ( m = rnewlineseek.exec( self.css ) ) ) {
		self._newlines.push( rnewlineseek.lastIndex - 1 );
	}

	// Begin rendering
	self.render();
	self._positions( self.branches );
}

// Methods
CSSTree.prototype = {

	// Starts process of reading the stylesheet
	render: function(){
		var self = this, peek = '';

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				self.i--;
				self.comment();
			}
			// Skip over all whitespace
			else if ( rwhitespace.exec( self.c ) ) {
				continue;
			}
			// Assume anything else is a selector/atrule
			else {
				self.i--;
				self.selector();
			}
		}
	},

	// Ignore comment blocks
	comment: function( nested ) {
		var self = this,
			position = new CSSTree.Position( self.i + 1 ),
			comment = '',
			peek = '';

		nested = nested || false;
		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// End of comment
			if ( self.c == '*' && peek == '/' ) {
				return self.branches.push(
					new CSSTree.Comment( comment + '*/', nested, position.markEnd( ++self.i + 1 ) )
				);
			}
			else {
				comment += self.c;
			}
		}
	},

	// Selctor looks for opening rule set or closing semicolon for oneliners
	selector: function(){
		var self = this,
			position = new CSSTree.Position( self.i + 1 ),
			selector = '',
			parts = [], nested = null, part = '',
			peek, branch;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment block
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment();
				position.markChunkStart( self.i + 1 );
			}
			// Atrule
			else if ( self.c == ';' ) {
				return self.branches.push(
					new CSSTree.AtRule( selector, position.markEnd( self.i ) )
				);
			}
			// Media atrule
			else if ( self.c == '{' && selector.trim()[ 0 ] == '@' ) {
				branch = self.nested( selector, position );
				branch.position = position.markEnd( self.i + 1 );
				return self.branches.push( branch );
			}
			// Selector for ruleset
			else if ( self.c == '{' ) {
				branch = new CSSTree.Selector( selector, self.rules( position ), position );
				position.markEnd( self.i + 1 );
				return self.branches.push( branch );
			}
			// Escape string
			else if ( self.c == '\\' ) {
				selector += self.c + self.css[ ++self.i ];
			}
			// Seek
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				selector += character + self.find( character ) + character;
			}
			// Seek
			else if ( self.c == '(' ) {
				selector += '(' + self.find( ')' ) + ')';
			}
			// Add to selector string
			else {
				selector += self.c;
			}
		}

		// Catch end of file queries without semi-colon
		if ( selector.trim().length ) {
			return self.branches.push(
				new CSSTree.AtRule( selector, position.markEnd( self.i ) )
			);
		}
	},

	// Rule Sets
	rules: function( parentPos ) {
		var self = this, rules = [], peek, rule, position;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				parentPos.markChunkEnd( self.i-- );
				self.comment( true );
				parentPos.markChunkStart( self.i + 1 );
			}
			// End of rules
			else if ( self.c == '}' ) {
				return rules;
			}
			// New rule
			else if ( ! rwhitespace.exec( self.c ) ) {
				position = new CSSTree.Position( self.i, parentPos );
				self.i--;
				rule = new CSSTree.Rule(
					self.property( position ).trim(),
					self.value( position ).trim(),
					position
				);

				// Break down the parts of the value, and push it
				position.markEnd( self.i, false );

				// Only add if there is an actual property
				if ( rule.property.length ) {
					rules.push( rule );
				}
			}
		}

		return rules;
	},

	// Property Names
	property: function( position ) {
		var self = this, property = '', peek;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Nested Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of property
			else if ( self.c == ':' ) {
				return property;
			}
			// Invalid CSS, but still end of property
			else if ( self.c == ';' || self.c == '}' ) {
				self.i--;
				return property;
			}
			else {
				property += self.c;
			}
		}

		return property;
	},

	// Values
	value: function( position ) {
		var self = this, value = '', character, peek;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of value
			else if ( self.c == ';' ) {
				return value;
			}
			// Watch for no semi-colon at end of set
			else if ( self.c == '}' ) {
				self.i--;
				return value;
			}
			// Seek strings
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				value += character + self.find( character ) + character;
			}
			// Seek groupings
			else if ( self.c == '(' ) {
				value += '(' + self.find( ')' ) + ')';
			}
			// Append
			else {
				value += self.c;
			}
		}

		return value;
	},

	// Parsing through escapable content
	find: function( endstring ) {
		var self = this, string = '';

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];

			// Escaped character
			if ( self.c == "\\" ) {
				string += self.c + self.css[ ++self.i ];
			}
			// Seek charactor hit
			else if ( self.c == endstring ) {
				return string;
			}
			// Append
			else {
				string += self.c;
			}
		}

		return string;
	},

	// Nested atrules
	nested: function( atrule, position ) {
		var self = this,
			string = '',
			peek, index, rule, subPosition,
			block = atrule.trim()[ 0 ] == '@' ?
				new CSSTree.AtRule( atrule, position ) :
				new CSSTree.Selector( atrule, null, position );

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Escaped string
			if ( self.c == '\\' ) {
				string += self.c + self.css[ ++self.i ];
			}
			// Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of property:value
			else if ( self.c == ';' ) {
				subPosition = new CSSTree.Position( self.i - string.trim().length );
				subPosition.markEnd( self.i );

				// String trimming & positioning
				string = string.trim();
				index = string.indexOf( ':' );

				// Build up rule property
				rule = new CSSTree.Rule(
					string.substr( 0, index ).trim(),
					string.substr( index + 1 ).trim(),
					subPosition
				);

				// Atrules don't startoff with rules
				if ( ! block.rules ) {
					block.rules = [];
				}

				// Parse out parts and add to branches rules
				block.rules.push( rule );
				string = '';
			}
			// Nested Block
			else if ( self.c == '{' ) {
				if ( ! block.branches ) {
					block.branches = [];
				}

				// Nested branches should start tracking at the first character, not whitespace
				string = string.replace( leftTrim, '' );

				// Travel down the tree
				subPosition = new CSSTree.Position( self.i - string.length, position );
				block.branches.push( self.nested( string, subPosition ) );
				string = '';
			}
			// Seek
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				string += character + self.find( character ) + character;
			}
			// Seek
			else if ( self.c == '(' ) {
				string += '(' + self.find( ')' ) + ')';
			}
			// End of block
			else if ( self.c == '}' ) {
				subPosition = new CSSTree.Position( self.i - string.length );

				// Assume any string left is a property:value definition
				if ( ( string = string.trim() ).length ) {
					index = string.indexOf( ':' );
					rule = new CSSTree.Rule(
						string.substr( 0, index ).trim(),
						string.substr( index + 1 ).trim(),
						subPosition.markEnd( self.i )
					);

					// Atrules don't startoff with rules
					if ( ! block.rules ) {
						block.rules = [];
					}

					// Parse out value parts and add to rules
					block.rules.push( rule );
				}

				block.position.markEnd( self.i + 1, false );
				return block;
			}
			// Append
			else {
				string += self.c;
			}
		}

		return block;
	},

	// Crawl the tree to finish position markup
	_positions: function( branches ) {
		var self = this;

		// Cycle through each branch for markings
		branches.forEach(function( branch ) {
			self._markPosition( branch.position );

			// Markup each rule positions
			if ( branch.rules ) {
				branch.rules.forEach(function( rule ) {
					if ( rule.position ) {
						self._markPosition( rule.position );
					}
				});
			}

			// Crawl nested branches
			if ( branch.branches ) {
				self._positions( branch.branches );
			}
		});
	},

	// Marks up position
	_markPosition: function( position ) {
		var self = this;

		// Mark branch positions
		position.start = self._charPosition( position.range.start );
		position.end = self._charPosition( position.range.end );
	},

	// Marks the line & character for the character position passed
	_charPosition: function( pos ) {
		var self = this, line = self._newlines.length, chracter = 0;

		while ( line-- ) {
			if ( pos > self._newlines[ line ] ) {
				chracter = self._newlines[ line ] + 1;
				break;
			}
		}

		return {
			line: line + 2,
			character: ( pos + 1 ) - chracter
		};
	}

};

// Expose to NodeJS/Window
if ( typeof module == 'object' && typeof module.exports == 'object' ) {
	module.exports = CSSTree;
}
else {
	global.CSSTree = CSSTree;
}
<commit_msg>Fixed a typo, and corrected some variable leaks into the global scope.<commit_after>var rnewlineseek = /(\r\n|\r|\n)/g,
	rwhitespace = /(\s|\t|\r\n|\r|\n)/,
	leftTrim = /^(\r\n|\r|\n|\t|\s)*/;

function CSSTree( css ) {
	var self = this, m;

	// Force instance of CSSTree
	if ( ! ( self instanceof CSSTree ) ) {
		return new CSSTree( css );
	}

	// Internals
	self.css = css || '';
	self.i = -1;
	self.length = self.css.length;
	self.branches = [];

	// Positional setup
	self._newlines = [];
	while ( ( m = rnewlineseek.exec( self.css ) ) ) {
		self._newlines.push( rnewlineseek.lastIndex - 1 );
	}

	// Begin rendering
	self.render();
	self._positions( self.branches );
}

// Methods
CSSTree.prototype = {

	// Starts process of reading the stylesheet
	render: function(){
		var self = this, peek = '';

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				self.i--;
				self.comment();
			}
			// Skip over all whitespace
			else if ( rwhitespace.exec( self.c ) ) {
				continue;
			}
			// Assume anything else is a selector/atrule
			else {
				self.i--;
				self.selector();
			}
		}
	},

	// Ignore comment blocks
	comment: function( nested ) {
		var self = this,
			position = new CSSTree.Position( self.i + 1 ),
			comment = '',
			peek = '';

		nested = nested || false;
		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// End of comment
			if ( self.c == '*' && peek == '/' ) {
				return self.branches.push(
					new CSSTree.Comment( comment + '*/', nested, position.markEnd( ++self.i + 1 ) )
				);
			}
			else {
				comment += self.c;
			}
		}
	},

	// Selctor looks for opening rule set or closing semicolon for oneliners
	selector: function(){
		var self = this,
			position = new CSSTree.Position( self.i + 1 ),
			selector = '',
			character,
			parts = [], nested = null, part = '',
			peek, branch;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment block
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment();
				position.markChunkStart( self.i + 1 );
			}
			// Atrule
			else if ( self.c == ';' ) {
				return self.branches.push(
					new CSSTree.AtRule( selector, position.markEnd( self.i ) )
				);
			}
			// Media atrule
			else if ( self.c == '{' && selector.trim()[ 0 ] == '@' ) {
				branch = self.nested( selector, position );
				branch.position = position.markEnd( self.i + 1 );
				return self.branches.push( branch );
			}
			// Selector for ruleset
			else if ( self.c == '{' ) {
				branch = new CSSTree.Selector( selector, self.rules( position ), position );
				position.markEnd( self.i + 1 );
				return self.branches.push( branch );
			}
			// Escape string
			else if ( self.c == '\\' ) {
				selector += self.c + self.css[ ++self.i ];
			}
			// Seek
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				selector += character + self.find( character ) + character;
			}
			// Seek
			else if ( self.c == '(' ) {
				selector += '(' + self.find( ')' ) + ')';
			}
			// Add to selector string
			else {
				selector += self.c;
			}
		}

		// Catch end of file queries without semi-colon
		if ( selector.trim().length ) {
			return self.branches.push(
				new CSSTree.AtRule( selector, position.markEnd( self.i ) )
			);
		}
	},

	// Rule Sets
	rules: function( parentPos ) {
		var self = this, rules = [], peek, rule, position;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				parentPos.markChunkEnd( self.i-- );
				self.comment( true );
				parentPos.markChunkStart( self.i + 1 );
			}
			// End of rules
			else if ( self.c == '}' ) {
				return rules;
			}
			// New rule
			else if ( ! rwhitespace.exec( self.c ) ) {
				position = new CSSTree.Position( self.i, parentPos );
				self.i--;
				rule = new CSSTree.Rule(
					self.property( position ).trim(),
					self.value( position ).trim(),
					position
				);

				// Break down the parts of the value, and push it
				position.markEnd( self.i, false );

				// Only add if there is an actual property
				if ( rule.property.length ) {
					rules.push( rule );
				}
			}
		}

		return rules;
	},

	// Property Names
	property: function( position ) {
		var self = this, property = '', peek;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Nested Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of property
			else if ( self.c == ':' ) {
				return property;
			}
			// Invalid CSS, but still end of property
			else if ( self.c == ';' || self.c == '}' ) {
				self.i--;
				return property;
			}
			else {
				property += self.c;
			}
		}

		return property;
	},

	// Values
	value: function( position ) {
		var self = this, value = '', character, peek;

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of value
			else if ( self.c == ';' ) {
				return value;
			}
			// Watch for no semi-colon at end of set
			else if ( self.c == '}' ) {
				self.i--;
				return value;
			}
			// Seek strings
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				value += character + self.find( character ) + character;
			}
			// Seek groupings
			else if ( self.c == '(' ) {
				value += '(' + self.find( ')' ) + ')';
			}
			// Append
			else {
				value += self.c;
			}
		}

		return value;
	},

	// Parsing through escapable content
	find: function( endstring ) {
		var self = this, string = '';

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];

			// Escaped character
			if ( self.c == "\\" ) {
				string += self.c + self.css[ ++self.i ];
			}
			// Seek charactor hit
			else if ( self.c == endstring ) {
				return string;
			}
			// Append
			else {
				string += self.c;
			}
		}

		return string;
	},

	// Nested atrules
	nested: function( atrule, position ) {
		var self = this,
			string = '',
			peek, index, rule, subPosition, character,
			block = atrule.trim()[ 0 ] == '@' ?
				new CSSTree.AtRule( atrule, position ) :
				new CSSTree.Selector( atrule, null, position );

		for ( ; ++self.i < self.length; ) {
			self.c = self.css[ self.i ];
			peek = self.css[ self.i + 1 ];

			// Escaped string
			if ( self.c == '\\' ) {
				string += self.c + self.css[ ++self.i ];
			}
			// Comment
			if ( self.c == '/' && peek == '*' ) {
				position.markChunkEnd( self.i-- );
				self.comment( true );
				position.markChunkStart( self.i + 1 );
			}
			// End of property:value
			else if ( self.c == ';' ) {
				subPosition = new CSSTree.Position( self.i - string.trim().length );
				subPosition.markEnd( self.i );

				// String trimming & positioning
				string = string.trim();
				index = string.indexOf( ':' );

				// Build up rule property
				rule = new CSSTree.Rule(
					string.substr( 0, index ).trim(),
					string.substr( index + 1 ).trim(),
					subPosition
				);

				// Atrules don't startoff with rules
				if ( ! block.rules ) {
					block.rules = [];
				}

				// Parse out parts and add to branches rules
				block.rules.push( rule );
				string = '';
			}
			// Nested Block
			else if ( self.c == '{' ) {
				if ( ! block.branches ) {
					block.branches = [];
				}

				// Nested branches should start tracking at the first character, not whitespace
				string = string.replace( leftTrim, '' );

				// Travel down the tree
				subPosition = new CSSTree.Position( self.i - string.length, position );
				block.branches.push( self.nested( string, subPosition ) );
				string = '';
			}
			// Seek
			else if ( self.c == "'" || self.c == '"' ) {
				character = self.c;
				string += character + self.find( character ) + character;
			}
			// Seek
			else if ( self.c == '(' ) {
				string += '(' + self.find( ')' ) + ')';
			}
			// End of block
			else if ( self.c == '}' ) {
				subPosition = new CSSTree.Position( self.i - string.length );

				// Assume any string left is a property:value definition
				if ( ( string = string.trim() ).length ) {
					index = string.indexOf( ':' );
					rule = new CSSTree.Rule(
						string.substr( 0, index ).trim(),
						string.substr( index + 1 ).trim(),
						subPosition.markEnd( self.i )
					);

					// Atrules don't startoff with rules
					if ( ! block.rules ) {
						block.rules = [];
					}

					// Parse out value parts and add to rules
					block.rules.push( rule );
				}

				block.position.markEnd( self.i + 1, false );
				return block;
			}
			// Append
			else {
				string += self.c;
			}
		}

		return block;
	},

	// Crawl the tree to finish position markup
	_positions: function( branches ) {
		var self = this;

		// Cycle through each branch for markings
		branches.forEach(function( branch ) {
			self._markPosition( branch.position );

			// Markup each rule positions
			if ( branch.rules ) {
				branch.rules.forEach(function( rule ) {
					if ( rule.position ) {
						self._markPosition( rule.position );
					}
				});
			}

			// Crawl nested branches
			if ( branch.branches ) {
				self._positions( branch.branches );
			}
		});
	},

	// Marks up position
	_markPosition: function( position ) {
		var self = this;

		// Mark branch positions
		position.start = self._charPosition( position.range.start );
		position.end = self._charPosition( position.range.end );
	},

	// Marks the line & character for the character position passed
	_charPosition: function( pos ) {
		var self = this, line = self._newlines.length, character = 0;

		while ( line-- ) {
			if ( pos > self._newlines[ line ] ) {
				character = self._newlines[ line ] + 1;
				break;
			}
		}

		return {
			line: line + 2,
			character: ( pos + 1 ) - character
		};
	}

};

// Expose to NodeJS/Window
if ( typeof module == 'object' && typeof module.exports == 'object' ) {
	module.exports = CSSTree;
}
else {
	global.CSSTree = CSSTree;
}
<|endoftext|>


<commit_before>var prefix = require('../');
var should = require('should');
var fs = require('fs');
var autoprefixer = require("autoprefixer");
var gutil = require('gulp-util');
var Stream = require('stream');
var es = require('event-stream');
require('mocha');

var testBrowsers = ["last 1 version", "> 1%", "ie 8", "ie 7"];
var testOptions = {
  cascade: false
};

var testfile = fs.readFileSync("./test/test.css","utf8");

describe('gulp-autoprefixer', function() {
  it('should prefix with defaults', function(done) {
    var stream = prefix();
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer.process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers', function(done) {
    var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7");
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7").process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers array', function(done) {
    var stream = prefix(testBrowsers);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testBrowsers).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();

  });

  it('should prefix with options', function(done) {
    var stream = prefix(testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers and options', function(done) {
    var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7", testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7", testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers array and options', function(done) {
    var stream = prefix(testBrowsers, testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testBrowsers, testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should work the same in stream mode', function(done) {
    var stream = prefix();
    var fakeFile = new gutil.File({
      contents: new Stream()
    });

    stream.on('data', function(data) {
      data.contents.pipe(es.wait(function(err, data) {
        String(data).should.equal(autoprefixer.process(testfile).css);
        done();
      }));
    });

    stream.write(fakeFile);
    fakeFile.contents.write(testfile);
    fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers', function(done) {
      var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7");
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
        String(data).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7").process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers array', function(done) {
      var stream = prefix(testBrowsers);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testBrowsers).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with options', function(done) {
      var stream = prefix(testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers and options', function(done) {
      var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7", testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7", testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers array and options', function(done) {
      var stream = prefix(testBrowsers, testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testBrowsers, testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should let null files pass through', function(done) {
      var stream = prefix(),
          n = 0;
      stream.pipe(es.through(function(file) {
        file.path.should.equal('null.md');
        (file.contents === null).should.be.true;
        n++;
      }, function() {
        n.should.equal(1);
        done();
      }));
      stream.write(new gutil.File({
        path: 'null.md',
        contents: null
      }));
      stream.end();
  });
});
<commit_msg>adding two more tests to ensure that the same settings are getting applied<commit_after>var prefix = require('../');
var should = require('should');
var fs = require('fs');
var autoprefixer = require("autoprefixer");
var gutil = require('gulp-util');
var Stream = require('stream');
var es = require('event-stream');
require('mocha');

var testBrowsers = ["last 1 version", "> 1%", "ie 8", "ie 7"];
var testOptions = {
  cascade: false
};

var testfile = fs.readFileSync("./test/test.css","utf8");

describe('gulp-autoprefixer', function() {
  it('should prefix with defaults', function(done) {
    var stream = prefix();
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer.process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers', function(done) {
    var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7");
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7").process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers array', function(done) {
    var stream = prefix(testBrowsers);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testBrowsers).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with options', function(done) {
    var stream = prefix(testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with cascade: false', function(done) {
    var stream = prefix({cascade: false});
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer({cascade: false}).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with cascade: true', function(done) {
    var stream = prefix({cascade: true});
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer({cascade: true}).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers and options', function(done) {
    var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7", testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7", testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should prefix with browsers array and options', function(done) {
    var stream = prefix(testBrowsers, testOptions);
    var fakeFile = new gutil.File({
      contents: new Buffer(testfile)
    });

    stream.on('data', function(newFile){
      String(newFile.contents).should.equal(autoprefixer(testBrowsers, testOptions).process(testfile).css);
      done();
    });

    stream.write(fakeFile);
    stream.end();
  });

  it('should work the same in stream mode', function(done) {
    var stream = prefix();
    var fakeFile = new gutil.File({
      contents: new Stream()
    });

    stream.on('data', function(data) {
      data.contents.pipe(es.wait(function(err, data) {
        String(data).should.equal(autoprefixer.process(testfile).css);
        done();
      }));
    });

    stream.write(fakeFile);
    fakeFile.contents.write(testfile);
    fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers', function(done) {
      var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7");
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
        String(data).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7").process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers array', function(done) {
      var stream = prefix(testBrowsers);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testBrowsers).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with options', function(done) {
      var stream = prefix(testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers and options', function(done) {
      var stream = prefix("last 1 version", "> 1%", "ie 8", "ie 7", testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer("last 1 version", "> 1%", "ie 8", "ie 7", testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should work the same in stream mode, with browsers array and options', function(done) {
      var stream = prefix(testBrowsers, testOptions);
      var fakeFile = new gutil.File({
        contents: new Stream()
      });

      stream.on('data', function(data) {
        data.contents.pipe(es.wait(function(err, data) {
          String(data).should.equal(autoprefixer(testBrowsers, testOptions).process(testfile).css);
          done();
        }));
      });

      stream.write(fakeFile);
      fakeFile.contents.write(testfile);
      fakeFile.contents.end();
  });

  it('should let null files pass through', function(done) {
      var stream = prefix(),
          n = 0;
      stream.pipe(es.through(function(file) {
        file.path.should.equal('null.md');
        (file.contents === null).should.be.true;
        n++;
      }, function() {
        n.should.equal(1);
        done();
      }));
      stream.write(new gutil.File({
        path: 'null.md',
        contents: null
      }));
      stream.end();
  });
});
<|endoftext|>


<commit_before>import _ from 'lodash'
import d3 from 'd3'

class TableChartRenderer {
  constructor (data, options, container) {
    this.setTableParams(data, options, container)
  }

  setTableParams (data, options, container) {
    console.info('-------- TableChartRenderer.setTableParams')
    this.container = container
    this.options = options
    this.data = data
    this.h = Math.max(data.length * options.cellHeight, 0)
    this.z = 160 //  extra margin space needed to add the "z" (parent) axis
    this.w = 3 * Math.max(options.headers.length * options.cellHeight, 0)
    this.xDomainProvided = typeof (options.xDomain) !== 'undefined' && options.xDomain.length > 0
    this.xDomain = this.xDomainProvided ? options.xDomain : options.headers.map(ind => ind.short_name)
    this.xScale = d3.scale.ordinal().domain(this.xDomain).rangeBands([0, this.w], 0.1)
    this.sourceFlow = _.flow(options.sourceColumn, this.xScale)
    this.x = _.flow(options.column, this.xScale)
    this.margin = options.margin
    this.scale = d3.scale.ordinal().domain(['bad', 'ok', 'good']).range(options.colors)
    this.domain = this.getDomain(data, options)
    this.yScale = d3.scale.ordinal().domain(this.domain).rangeBands([0, this.h], 0.1)
    this.y = _.flow(options.seriesName, this.yScale)
    this.transform = (d, i) => `translate(${this.z}, ${this.y(d) + 10})`
    this.targets = this.getTargets(options.headers)
    this.fill = d => !_.isNull(d.value) && _.isFinite(d.value) ? this.scale(this.targets[d.indicator.id](d.value)) : '#FFFFFF'
    this.svg = d3.select(container)
  }

  update (data, options, container) {
    this.setTableParams(data, options, container)
    this.render()
  }

  render () {
    console.info('-------- TableChartRenderer.render')
    this.prepContainer()
    this.renderRows()
    this.renderCells()
    this.renderXAxis()
    this.renderYAxis()
    this.renderZAxis()
    this.renderFooter()
    this.renderLegend()
  }

  // =========================================================================== //
  //                                    RENDER                                   //
  // =========================================================================== //
  prepContainer () {
    // hacky way to scale the view box.. this should be done by taking into account the user's screen size
    const calculatedHeightScale = 1 + (this.options.headers.length - 8) / 10
    const viewBoxHeightScale = calculatedHeightScale < 1 ? calculatedHeightScale : 1
    const viewBoxWidth = this.w + this.margin.left + this.margin.right - 150
    const viewBox = '0 -50 ' + viewBoxWidth + ' ' + ((this.h * viewBoxHeightScale) + this.margin.top + this.margin.bottom + 50)
    this.svg.attr({
      'viewBox': viewBox,
      'width': (this.w + this.margin.left + this.margin.right),
      'height': (this.h + this.margin.top + this.margin.bottom)
    })
   .datum(this.data)
    this.svg.select('.margin').attr('transform', 'translate(-75, ' + this.margin.top + ')')
  }

  // ROWS
  // ---------------------------------------------------------------------------
  renderRows () {
    const g = this.svg.select('.data')
    g.on('mouseout', () => this.onRowOut.apply(this))
    const rows = g.selectAll('.row').data(this.data)
    rows.enter().append('g').attr({'class': 'row', 'transform': this.transform})
    rows.exit().transition().duration(300).style('opacity', 0).remove()
    rows.on('click', (d, i) => this.onRowClick([d, i]))
    rows.on('mouseover', (d, i) => this.onRowOver([d, i])).transition().duration(750).attr('transform', this.transform)
    this.rows = rows
  }

  // CELLS
  // ---------------------------------------------------------------------------
  renderCells () {
    const cells = this.rows.selectAll('.cell').data(this.options.values)
    cells.exit().transition().duration(300).style('opacity', 0).remove()
    cells.attr('id', d => [d.location.name, d.indicator.short_name].join('-'))
    cells.style('cursor', _.isFunction(this.options.onClick) ? 'pointer' : 'initial')
    cells.on('mousemove', this.options.onMouseMove)
    cells.on('mouseout', this.options.onMouseOut)
    cells.on('click', this.options.onClick)
    cells.transition().duration(500).style('fill', this.fill)
      .attr({
        'height': this.yScale.rangeBand(),
        'width': this.xScale.rangeBand(),
        'x': this.x
      })

    const cg = cells.enter().append('g')
    cg.append('rect')
      .attr({
        'class': 'cell',
        'height': this.yScale.rangeBand(),
        'x': this.x,
        'width': this.xScale.rangeBand()
      })
      .style({ 'opacity': 0, 'fill': this.fill })
      .transition().duration(500)
      .style('opacity', 1)
    cg.append('text')
      .attr({
        'height': this.yScale.rangeBand(),
        'x': d => this.x(d) + this.xScale.rangeBand() / 2,
        'y': (this.options.cellHeight / 2) - (this.options.cellFontSize / 4.3),
        'width': this.xScale.rangeBand(),
        'dominant-baseline': 'central',
        'text-anchor': 'middle',
        'font-weight': 'bold'
      })
      .style({'font-size': this.options.cellFontSize})
      .text(d => d.displayValue)
      .transition().duration(500)
  }

  // X AXIS
  // ---------------------------------------------------------------------------
  renderXAxis () {
    this.svg.select('.x.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(' + this.z + ',-40)'})
      .call(d3.svg.axis().scale(this.xScale).orient('top').outerTickSize(0))
    this.svg.selectAll('.x.axis text').on('click', (d, i) => this.onSetSort(d, i))
    this.svg.selectAll('.x.axis text')
      .attr({'transform': 'rotate(-45)'})
      .call(this.wrap, this.xScale.rangeBand())
  }

  // Y AXIS
  // ---------------------------------------------------------------------------
  renderYAxis () {
    this.svg.select('.y.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(' + this.z + ',10)'})
      .call(d3.svg.axis().scale(this.yScale).orient('left').outerTickSize(0))
    this.svg.selectAll('.y.axis text')
      .style('font-size', this.options.fontSize + 2)
      .on('click', (d, i) => this.options.onRowClick(d, i, this))
  }

  // Z AXIS
  // ---------------------------------------------------------------------------
  renderZAxis () {
    this.svg.select('.z.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(20,10)'})
      .call(d3.svg.axis()
        .scale(this.yScale)
        .tickFormat(d => this.options.parent_location_map[d].parent_location__name)
        .orient('left')
        .outerTickSize(0))
    this.svg.selectAll('.z.axis text')
      .style('font-size', this.options.fontSize)
      .on('click', (d, i) => this.options.onRowClick(this.options.parent_location_map[d].parent_location__name, i, this))
  }

  // FOOTER
  // ---------------------------------------------------------------------------
  renderFooter () {
    const singleRowIndicators = this.options.headers // chartData[0].values
    const sourceFooter = this.svg.select('.source-footer').attr({'transform': 'translate(0,' + 10 + ')'})
    const sourceCell = sourceFooter.selectAll('.source-cell').data(singleRowIndicators)
    const sourceG = sourceCell.enter().append('g')
    sourceG.append('rect')
      .attr({
        'class': 'cell',
        'height': this.yScale.rangeBand() * 1.5,
        'transform': `translate(${this.z}, ${this.h})`,
        'x': this.sourceFlow,
        'width': this.xScale.rangeBand()
      })
      .style({ 'opacity': 0, 'fill': '#F1F1F1' })
      .transition().duration(500)
      .style('opacity', 1)

    sourceG.append('text')
      .attr({
        'height': this.yScale.rangeBand(),
        'transform': `translate(${this.z}, ${this.h})`,
        'x': d => this.sourceFlow(d) + this.options.cellHeight * 1.33,
        'y': this.options.cellHeight / 2,
        'width': this.xScale.rangeBand(),
        'dominant-baseline': 'central',
        'text-anchor': 'middle',
        'font-weight': 'bold'
      })
      .text(d => d.source_name)
      .transition().duration(500)
      .call(this.wrap, this.xScale.rangeBand())
  }

  // LEGEND
  // ---------------------------------------------------------------------------
  renderLegend () {
    if (this.options.legend) {
      this.svg.select('.legend')
        .call(this.options.legend)
        .attr('transform', () => {
          const bbox = this.getBoundingClientRect()
          const dx = this.w + this.margin.right - bbox.width
          return `translate(${dx}, 0)`
        })
    } else {
      this.svg.select('.legend').selectAll('*').remove()
    }
  }

  // =========================================================================== //
  //                                EVENT HANDLERS                               //
  // =========================================================================== //
  onRowOver (d) {
    this.rows
      .transition().duration(300)
      .style('cursor', 'pointer')
      .style('opacity', e => this.options.seriesName(e) === d[0].name ? 1 : 0.3)
  }

  onRowClick (d) {
    // console.log('row clicked', d)
  }

  onRowOut () {
    this.rows.transition().duration(300).style('opacity', 1)
  }

  onSetSort (d) {
    // Fist click, order ascending, Second order descending, third order default
    if (d === this.sortCol && this.sortDirection === -1) {
      this.sortCol = null
    } else if (d === this.sortCol && this.sortDirection === 1) {
      this.sortCol = d
      this.sortDirection = -1
    } else {
      this.sortCol = d
      this.sortDirection = 1
    }

    this.update(this.data, this.options, this.container)
  }

  // =========================================================================== //
  //                                     UTILITIES                               //
  // =========================================================================== //
  getTargets (headers) {
    // COLORS - Move from here once the user can set a pallette
    // ---------------------------------------------------------------------------
    return _(headers).indexBy('id').mapValues(ind => {
      const boundsReversed = ind.bad_bound > ind.good_bound
      const names = boundsReversed ? ['good', 'ok', 'bad'] : ['bad', 'ok', 'good']
      const extents = boundsReversed ? [ ind.good_bound, ind.bad_bound ] : [ ind.bad_bound, ind.good_bound ]
      return d3.scale.threshold().domain(extents).range(names)
    })
    .value()
  }

  getDomain (data, options) {
    // if there is a sortCol set, order the data that way.
    if (this.sortCol) {
      let sortValue = _.partial(this.getSortValue.bind(this), _, this.sortCol)
      var domain = _(data).sortBy(sortValue, this).map(options.seriesName).value()
    } else {
      // if not, show default.  This also applies to the third click of a header
      domain = options.default_sort_order
      this.sortDirection = 1
    }
    if (this.sortDirection === -1) {
      domain = domain.reverse()
    }

    // For empty data points i need to add the x axis domain items explicitly //
    // otherwise the domain will be less ( and different the ) the yScale //
    // see trello : https://trello.com/c/bCwyqSWs/277-display-bug-when-creating-table-chart //
    if (domain.length < options.default_sort_order.length) {
      const diff = options.default_sort_order.filter(x => domain.indexOf(x) < 0)
      domain = domain.concat(diff)
    }

    return domain
  }

  getSortValue (s, sortCol) {
    const options = this.options
    if (sortCol === null) {
      return options.seriesName(s)
    }
    return options.value(_.find(options.values(s), d => {
      if (d.value === null || !_.isFinite(d.value)) {
        d.value = this.sortDirection !== 1 ? -Infinity : Infinity
      }
      return options.column(d) === sortCol
    }))
  }

  wrap (text, width) {
    text.each(function () {
      var text = d3.select(this)
      var words = text.text().split(/\s+/)
      var line = []
      var lineNumber = 0
      var lineHeight = 1.1 // ems
      var y = text.attr('y') - 10
      var x = text.attr('x') !== null ? text.attr('x') : 0
      var dy = 0
      var tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em')
      var i = 0
      for (i = 0; i < words.length; i += 1) {
        var word = words[i]
        line.push(word)
        tspan.text(line.join(' '))
        if (tspan.node().getComputedTextLength() > width) {
          line.pop()
          tspan.text(line.join(' '))
          line = [word]
          tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', ++lineNumber * lineHeight + dy + 'em').text(word)
        }
      }
    })
  }

}

export default TableChartRenderer
<commit_msg>Fix table chart sorting<commit_after>import _ from 'lodash'
import d3 from 'd3'

class TableChartRenderer {
  constructor (data, options, container) {
    this.setTableParams(data, options, container)
  }

  setTableParams (data, options, container) {
    console.info('-------- TableChartRenderer.setTableParams')
    this.container = container
    this.options = options
    this.data = data
    this.h = Math.max(data.length * options.cellHeight, 0)
    this.z = 160 //  extra margin space needed to add the "z" (parent) axis
    this.w = 3 * Math.max(options.headers.length * options.cellHeight, 0)
    this.xDomainProvided = typeof (options.xDomain) !== 'undefined' && options.xDomain.length > 0
    this.xDomain = this.xDomainProvided ? options.xDomain : options.headers.map(ind => ind.short_name)
    this.xScale = d3.scale.ordinal().domain(this.xDomain).rangeBands([0, this.w], 0.1)
    this.sourceFlow = _.flow(options.sourceColumn, this.xScale)
    this.x = _.flow(options.column, this.xScale)
    this.margin = options.margin
    this.scale = d3.scale.ordinal().domain(['bad', 'ok', 'good']).range(options.colors)
    this.domain = this.getDomain(data, options)
    this.yScale = d3.scale.ordinal().domain(this.domain).rangeBands([0, this.h], 0.1)
    this.y = _.flow(options.seriesName, this.yScale)
    this.transform = (d, i) => `translate(${this.z}, ${this.y(d) + 10})`
    this.targets = this.getTargets(options.headers)
    this.fill = d => !_.isNull(d.value) && _.isFinite(d.value) ? this.scale(this.targets[d.indicator.id](d.value)) : '#FFFFFF'
    this.svg = d3.select(container)
  }

  update (data, options, container) {
    this.setTableParams(data, options, container)
    this.render()
  }

  render () {
    console.info('-------- TableChartRenderer.render')
    this.prepContainer()
    this.renderRows()
    this.renderCells()
    this.renderXAxis()
    this.renderYAxis()
    this.renderZAxis()
    this.renderFooter()
    this.renderLegend()
  }

  // =========================================================================== //
  //                                    RENDER                                   //
  // =========================================================================== //
  prepContainer () {
    // hacky way to scale the view box.. this should be done by taking into account the user's screen size
    const calculatedHeightScale = 1 + (this.options.headers.length - 8) / 10
    const viewBoxHeightScale = calculatedHeightScale < 1 ? calculatedHeightScale : 1
    const viewBoxWidth = this.w + this.margin.left + this.margin.right - 150
    const viewBox = '0 -50 ' + viewBoxWidth + ' ' + ((this.h * viewBoxHeightScale) + this.margin.top + this.margin.bottom + 50)
    this.svg.attr({
      'viewBox': viewBox,
      'width': (this.w + this.margin.left + this.margin.right),
      'height': (this.h + this.margin.top + this.margin.bottom)
    })
   .datum(this.data)
    this.svg.select('.margin').attr('transform', 'translate(-75, ' + this.margin.top + ')')
  }

  // ROWS
  // ---------------------------------------------------------------------------
  renderRows () {
    const g = this.svg.select('.data')
    g.on('mouseout', () => this.onRowOut.apply(this))
    const rows = g.selectAll('.row').data(this.data)
    rows.enter().append('g').attr({'class': 'row', 'transform': this.transform})
    rows.exit().transition().duration(300).style('opacity', 0).remove()
    rows.on('click', (d, i) => this.onRowClick([d, i]))
    rows.on('mouseover', (d, i) => this.onRowOver([d, i]))
      .transition().duration(750)
      .attr('transform', this.transform)
      .attr('pointer-events', 'none')
      .each('end', () => rows.attr('pointer-events', null))
    this.rows = rows
  }

  // CELLS
  // ---------------------------------------------------------------------------
  renderCells () {
    const cells = this.rows.selectAll('.cell').data(this.options.values)
    cells.exit().transition().duration(300).style('opacity', 0).remove()
    cells.attr('id', d => [d.location.name, d.indicator.short_name].join('-'))
    cells.style('cursor', _.isFunction(this.options.onClick) ? 'pointer' : 'initial')
    cells.on('mousemove', this.options.onMouseMove)
    cells.on('mouseout', this.options.onMouseOut)
    cells.on('click', this.options.onClick)
    cells.transition().duration(500).style('fill', this.fill)
      .attr({
        'height': this.yScale.rangeBand(),
        'width': this.xScale.rangeBand(),
        'x': this.x
      })

    const cg = cells.enter().append('g')
    cg.append('rect')
      .attr({
        'class': 'cell',
        'height': this.yScale.rangeBand(),
        'x': this.x,
        'width': this.xScale.rangeBand()
      })
      .style({ 'opacity': 0, 'fill': this.fill })
      .transition().duration(500)
      .style('opacity', 1)
    cg.append('text')
      .attr({
        'height': this.yScale.rangeBand(),
        'x': d => this.x(d) + this.xScale.rangeBand() / 2,
        'y': (this.options.cellHeight / 2) - (this.options.cellFontSize / 4.3),
        'width': this.xScale.rangeBand(),
        'dominant-baseline': 'central',
        'text-anchor': 'middle',
        'font-weight': 'bold'
      })
      .style({'font-size': this.options.cellFontSize})
      .text(d => d.displayValue)
      .transition().duration(500)
  }

  // X AXIS
  // ---------------------------------------------------------------------------
  renderXAxis () {
    this.svg.select('.x.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(' + this.z + ',-40)'})
      .call(d3.svg.axis().scale(this.xScale).orient('top').outerTickSize(0))
    this.svg.selectAll('.x.axis text').on('click', (d, i) => this.onSetSort(d, i))
    this.svg.selectAll('.x.axis text')
      .attr({'transform': 'rotate(-45)'})
      .call(this.wrap, this.xScale.rangeBand())
  }

  // Y AXIS
  // ---------------------------------------------------------------------------
  renderYAxis () {
    this.svg.select('.y.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(' + this.z + ',10)'})
      .call(d3.svg.axis().scale(this.yScale).orient('left').outerTickSize(0))
    this.svg.selectAll('.y.axis text')
      .style('font-size', this.options.fontSize + 2)
      .on('click', (d, i) => this.options.onRowClick(d, i, this))
  }

  // Z AXIS
  // ---------------------------------------------------------------------------
  renderZAxis () {
    this.svg.select('.z.axis')
      .transition().duration(500)
      .attr({'transform': 'translate(20,10)'})
      .call(d3.svg.axis()
        .scale(this.yScale)
        .tickFormat(d => this.options.parent_location_map[d].parent_location__name)
        .orient('left')
        .outerTickSize(0))
    this.svg.selectAll('.z.axis text')
      .style('font-size', this.options.fontSize)
      .on('click', (d, i) => this.options.onRowClick(this.options.parent_location_map[d].parent_location__name, i, this))
  }

  // FOOTER
  // ---------------------------------------------------------------------------
  renderFooter () {
    const singleRowIndicators = this.options.headers // chartData[0].values
    const sourceFooter = this.svg.select('.source-footer').attr({'transform': 'translate(0,' + 10 + ')'})
    const sourceCell = sourceFooter.selectAll('.source-cell').data(singleRowIndicators)
    const sourceG = sourceCell.enter().append('g')
    sourceG.append('rect')
      .attr({
        'class': 'cell',
        'height': this.yScale.rangeBand() * 1.5,
        'transform': `translate(${this.z}, ${this.h})`,
        'x': this.sourceFlow,
        'width': this.xScale.rangeBand()
      })
      .style({ 'opacity': 0, 'fill': '#F1F1F1' })
      .transition().duration(500)
      .style('opacity', 1)

    sourceG.append('text')
      .attr({
        'height': this.yScale.rangeBand(),
        'transform': `translate(${this.z}, ${this.h})`,
        'x': d => this.sourceFlow(d) + this.options.cellHeight * 1.33,
        'y': this.options.cellHeight / 2,
        'width': this.xScale.rangeBand(),
        'dominant-baseline': 'central',
        'text-anchor': 'middle',
        'font-weight': 'bold'
      })
      .text(d => d.source_name)
      .transition().duration(500)
      .call(this.wrap, this.xScale.rangeBand())
  }

  // LEGEND
  // ---------------------------------------------------------------------------
  renderLegend () {
    if (this.options.legend) {
      this.svg.select('.legend')
        .call(this.options.legend)
        .attr('transform', () => {
          const bbox = this.getBoundingClientRect()
          const dx = this.w + this.margin.right - bbox.width
          return `translate(${dx}, 0)`
        })
    } else {
      this.svg.select('.legend').selectAll('*').remove()
    }
  }

  // =========================================================================== //
  //                                EVENT HANDLERS                               //
  // =========================================================================== //
  onRowOver (d) {
    this.rows
      .transition().duration(300)
      .style('cursor', 'pointer')
      .style('opacity', e => this.options.seriesName(e) === d[0].name ? 1 : 0.3)
  }

  onRowClick (d) {
    // console.log('row clicked', d)
  }

  onRowOut () {
    this.rows.transition().duration(300).style('opacity', 1)
  }

  onSetSort (d) {
    // Fist click, order ascending, Second order descending, third order default
    if (d === this.sortCol && this.sortDirection === -1) {
      this.sortCol = null
    } else if (d === this.sortCol && this.sortDirection === 1) {
      this.sortCol = d
      this.sortDirection = -1
    } else {
      this.sortCol = d
      this.sortDirection = 1
    }

    this.update(this.data, this.options, this.container)
  }

  // =========================================================================== //
  //                                     UTILITIES                               //
  // =========================================================================== //
  getTargets (headers) {
    // COLORS - Move from here once the user can set a pallette
    // ---------------------------------------------------------------------------
    return _(headers).indexBy('id').mapValues(ind => {
      const boundsReversed = ind.bad_bound > ind.good_bound
      const names = boundsReversed ? ['good', 'ok', 'bad'] : ['bad', 'ok', 'good']
      const extents = boundsReversed ? [ ind.good_bound, ind.bad_bound ] : [ ind.bad_bound, ind.good_bound ]
      return d3.scale.threshold().domain(extents).range(names)
    })
    .value()
  }

  getDomain (data, options) {
    // if there is a sortCol set, order the data that way.
    if (this.sortCol) {
      let sortValue = _.partial(this.getSortValue.bind(this), _, this.sortCol)
      var domain = _(data).sortBy(sortValue, this).map(options.seriesName).value()
    } else {
      // if not, show default.  This also applies to the third click of a header
      domain = options.default_sort_order
      this.sortDirection = 1
    }
    if (this.sortDirection === -1) {
      domain = domain.reverse()
    }

    // For empty data points i need to add the x axis domain items explicitly //
    // otherwise the domain will be less ( and different the ) the yScale //
    // see trello : https://trello.com/c/bCwyqSWs/277-display-bug-when-creating-table-chart //
    if (domain.length < options.default_sort_order.length) {
      const diff = options.default_sort_order.filter(x => domain.indexOf(x) < 0)
      domain = domain.concat(diff)
    }

    return domain
  }

  getSortValue (s, sortCol) {
    const options = this.options
    if (sortCol === null) {
      return options.seriesName(s)
    }
    return options.value(_.find(options.values(s), d => {
      if (d.value === null || !_.isFinite(d.value)) {
        d.value = this.sortDirection !== 1 ? -Infinity : Infinity
      }
      return options.column(d) === sortCol
    }))
  }

  wrap (text, width) {
    text.each(function () {
      var text = d3.select(this)
      var words = text.text().split(/\s+/)
      var line = []
      var lineNumber = 0
      var lineHeight = 1.1 // ems
      var y = text.attr('y') - 10
      var x = text.attr('x') !== null ? text.attr('x') : 0
      var dy = 0
      var tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em')
      var i = 0
      for (i = 0; i < words.length; i += 1) {
        var word = words[i]
        line.push(word)
        tspan.text(line.join(' '))
        if (tspan.node().getComputedTextLength() > width) {
          line.pop()
          tspan.text(line.join(' '))
          line = [word]
          tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', ++lineNumber * lineHeight + dy + 'em').text(word)
        }
      }
    })
  }

}

export default TableChartRenderer
<|endoftext|>


<commit_before><commit_msg>Fix #1651: Added special case for index-1.html<commit_after><|endoftext|>


<commit_before>var Promise = require( 'bluebird' ),
    request = Promise.promisifyAll( require( 'superagent' ) ),
    _ = require( 'lodash' );

var resources = {
    defaults: {
        provider: { name: 'myact-provider-rss', package: {} },
        agent: { config: { url: 'http://www.andrewduthie.com/feed.xml' }, secret: 'not-so-secret' }
    },
    depends: {
        agent: [ 'provider' ]
    },
    cached: {}
};

var fetch = module.exports = function( resource, root, token, properties ) {
    if ( 'undefined' === typeof properties ) {
        properties = {};
    }

    // Return early if we already have a cached resource
    if ( 'undefined' !== typeof resources.cached[ resource ] ) {
        return Promise.resolve( resources.cached[ resource ] );
    }

    // First resolve dependencies
    var dependsResolved;
    if ( 'undefined' !== typeof resources.depends[ resource ] ) {
        dependsResolved = Promise.all( resources.depends[ resource ].map(function( depend ) {
            return fetch( depend, root, token, properties );
        }) );
    } else {
        dependsResolved = Promise.resolve();
    }

    return dependsResolved.spread(function() {
        // Inject depended resource as foreign key ID
        var args = _.toArray( arguments ) || [];
        args.forEach(function( dependResource, i ) {
            if ( 'number' === typeof dependResource.id ) {
                properties[ resources.depends[ resource ][ i ] + '_id' ] = dependResource.id;
            }
        });

        // Extend defaults with custom properties
        properties = _.extend({}, resources.defaults[ resource ], properties );

        // If we've made it this far, create a new resource
        return request
            .post( root + '/' + resource )
            .set( 'Authorization', 'JWT ' + token )
            .send( properties )
            .endAsync()
            .then(function( res ) {
                return resources.cached[ resource ] = res.body[ resource ];
            });
    });
};<commit_msg>Add activity option to resource test helper<commit_after>var Promise = require( 'bluebird' ),
    request = Promise.promisifyAll( require( 'superagent' ) ),
    _ = require( 'lodash' );

var resources = {
    defaults: {
        provider: { name: 'myact-provider-rss', package: {} },
        agent: { config: { url: 'http://www.andrewduthie.com/feed.xml' }, secret: 'not-so-secret' },
        activity: { key: '0', data: {} }
    },
    depends: {
        agent: [ 'provider' ],
        activity: [ 'agent' ]
    },
    cached: {}
};

var fetch = module.exports = function( resource, root, token, properties ) {
    if ( 'undefined' === typeof properties ) {
        properties = {};
    }

    // Return early if we already have a cached resource
    if ( 'undefined' !== typeof resources.cached[ resource ] ) {
        return Promise.resolve( resources.cached[ resource ] );
    }

    // First resolve dependencies
    var dependsResolved;
    if ( 'undefined' !== typeof resources.depends[ resource ] ) {
        dependsResolved = Promise.all( resources.depends[ resource ].map(function( depend ) {
            return fetch( depend, root, token, properties );
        }) );
    } else {
        dependsResolved = Promise.resolve();
    }

    return dependsResolved.spread(function() {
        // Inject depended resource as foreign key ID
        var args = _.toArray( arguments ) || [];
        args.forEach(function( dependResource, i ) {
            if ( 'number' === typeof dependResource.id ) {
                properties[ resources.depends[ resource ][ i ] + '_id' ] = dependResource.id;
            }
        });

        // Extend defaults with custom properties
        properties = _.extend({}, resources.defaults[ resource ], properties );

        // If we've made it this far, create a new resource
        return request
            .post( root + '/' + resource )
            .set( 'Authorization', 'JWT ' + token )
            .send( properties )
            .endAsync()
            .then(function( res ) {
                return resources.cached[ resource ] = res.body[ resource ];
            });
    });
};<|endoftext|>


<commit_before>'use strict';

var di = require('didi');

/**
 * Bootstrap an injector from a list of modules, instantiating a number of default components
 *
 * @ignore
 * @param {Array<didi.Module>} bootstrapModules
 *
 * @return {didi.Injector} a injector to use to access the components
 */
function bootstrap(bootstrapModules) {

  var modules = [],
      components = [];

  function hasModule(m) {
    return modules.indexOf(m) >= 0;
  }

  function addModule(m) {
    modules.push(m);
  }

  function visit(m) {
    if (hasModule(m)) {
      return;
    }

    (m.__depends__ || []).forEach(visit);

    if (hasModule(m)) {
      return;
    }

    addModule(m);

    (m.__init__ || []).forEach(function(c) {
      components.push(c);
    });
  }

  bootstrapModules.forEach(visit);

  var injector = new di.Injector(modules);

  components.forEach(function(c) {

    try {
      // eagerly resolve component (fn or string)
      injector[typeof c === 'string' ? 'get' : 'invoke'](c);
    } catch (e) {
      console.error('Failed to instantiate component');
      console.error(e.stack);

      throw e;
    }
  });

  return injector;
}

/**
 * Creates an injector from passed options.
 *
 * @ignore
 * @param  {Object} options
 * @return {didi.Injector}
 */
function createInjector(options) {

  options = options || {};

  var configModule = {
    'config': ['value', options]
  };

  var coreModule = require('./core');

  var modules = [ configModule, coreModule ].concat(options.modules || []);

  return bootstrap(modules);
}

/**
 * The main diagram-js entry point that bootstraps the diagram with the given
 * configuration.
 *
 * To register extensions with the diagram, pass them as Array<didi.Module> to the constructor.
 *
 * @class djs.Diagram
 * @memberOf djs
 * @constructor
 *
 * @example
 *
 *
Creating a plug-in that logs whenever a shape is added to the canvas.

 *
 * // plug-in implemenentation
 * function MyLoggingPlugin(eventBus) {
 *   eventBus.on('shape.added', function(event) {
 *     console.log('shape ', event.shape, ' was added to the diagram');
 *   });
 * }
 *
 * // export as module
 * module.exports = {
 *   __init__: [ 'myLoggingPlugin' ],
 *     myLoggingPlugin: [ 'type', MyLoggingPlugin ]
 * };
 *
 *
 * // instantiate the diagram with the new plug-in
 *
 * var diagram = new Diagram({ modules: [ require('path-to-my-logging-plugin') ] });
 *
 * diagram.invoke([ 'canvas', function(canvas) {
 *   // add shape to drawing canvas
 *   canvas.addShape({ x: 10, y: 10 });
 * });
 *
 * // 'shape ... was added to the diagram' logged to console
 *
 * @param {Object} options
 * @param {Array<didi.Module>} [options.modules] external modules to instantiate with the diagram
 * @param {didi.Injector} [injector] an (optional) injector to bootstrap the diagram with
 */
function Diagram(options, injector) {

  // create injector unless explicitly specified
  this.injector = injector = injector || createInjector(options);

  // API

  /**
   * Resolves a diagram service
   *
   * @method Diagram#get
   *
   * @param {String} name the name of the diagram service to be retrieved
   * @param {Boolean} [strict=true] if false, resolve missing services to null
   */
  this.get = injector.get;

  /**
   * Executes a function into which diagram services are injected
   *
   * @method Diagram#invoke
   *
   * @param {Function|Object[]} fn the function to resolve
   * @param {Object} locals a number of locals to use to resolve certain dependencies
   */
  this.invoke = injector.invoke;

  // init

  // indicate via event

  /**
   * An event indicating that all plug-ins are loaded.
   *
   * Use this event to fire other events to interested plug-ins
   *
   * @memberOf Diagram
   *
   * @event diagram.init
   *
   * @example
   *
   * eventBus.on('diagram.init', function() {
   *   eventBus.fire('my-custom-event', { foo: 'BAR' });
   * });
   *
   * @type {Object}
   */
  this.get('eventBus').fire('diagram.init');
}

module.exports = Diagram;

/**
 * Destroys the diagram
 *
 * @method  Diagram#destroy
 */
Diagram.prototype.destroy = function() {
  this.get('eventBus').fire('diagram.destroy');
};<commit_msg>feat(Diagram): add ability to clear existing contents<commit_after>'use strict';

var di = require('didi');

/**
 * Bootstrap an injector from a list of modules, instantiating a number of default components
 *
 * @ignore
 * @param {Array<didi.Module>} bootstrapModules
 *
 * @return {didi.Injector} a injector to use to access the components
 */
function bootstrap(bootstrapModules) {

  var modules = [],
      components = [];

  function hasModule(m) {
    return modules.indexOf(m) >= 0;
  }

  function addModule(m) {
    modules.push(m);
  }

  function visit(m) {
    if (hasModule(m)) {
      return;
    }

    (m.__depends__ || []).forEach(visit);

    if (hasModule(m)) {
      return;
    }

    addModule(m);

    (m.__init__ || []).forEach(function(c) {
      components.push(c);
    });
  }

  bootstrapModules.forEach(visit);

  var injector = new di.Injector(modules);

  components.forEach(function(c) {

    try {
      // eagerly resolve component (fn or string)
      injector[typeof c === 'string' ? 'get' : 'invoke'](c);
    } catch (e) {
      console.error('Failed to instantiate component');
      console.error(e.stack);

      throw e;
    }
  });

  return injector;
}

/**
 * Creates an injector from passed options.
 *
 * @ignore
 * @param  {Object} options
 * @return {didi.Injector}
 */
function createInjector(options) {

  options = options || {};

  var configModule = {
    'config': ['value', options]
  };

  var coreModule = require('./core');

  var modules = [ configModule, coreModule ].concat(options.modules || []);

  return bootstrap(modules);
}

/**
 * The main diagram-js entry point that bootstraps the diagram with the given
 * configuration.
 *
 * To register extensions with the diagram, pass them as Array<didi.Module> to the constructor.
 *
 * @class djs.Diagram
 * @memberOf djs
 * @constructor
 *
 * @example
 *
 *
Creating a plug-in that logs whenever a shape is added to the canvas.

 *
 * // plug-in implemenentation
 * function MyLoggingPlugin(eventBus) {
 *   eventBus.on('shape.added', function(event) {
 *     console.log('shape ', event.shape, ' was added to the diagram');
 *   });
 * }
 *
 * // export as module
 * module.exports = {
 *   __init__: [ 'myLoggingPlugin' ],
 *     myLoggingPlugin: [ 'type', MyLoggingPlugin ]
 * };
 *
 *
 * // instantiate the diagram with the new plug-in
 *
 * var diagram = new Diagram({ modules: [ require('path-to-my-logging-plugin') ] });
 *
 * diagram.invoke([ 'canvas', function(canvas) {
 *   // add shape to drawing canvas
 *   canvas.addShape({ x: 10, y: 10 });
 * });
 *
 * // 'shape ... was added to the diagram' logged to console
 *
 * @param {Object} options
 * @param {Array<didi.Module>} [options.modules] external modules to instantiate with the diagram
 * @param {didi.Injector} [injector] an (optional) injector to bootstrap the diagram with
 */
function Diagram(options, injector) {

  // create injector unless explicitly specified
  this.injector = injector = injector || createInjector(options);

  // API

  /**
   * Resolves a diagram service
   *
   * @method Diagram#get
   *
   * @param {String} name the name of the diagram service to be retrieved
   * @param {Boolean} [strict=true] if false, resolve missing services to null
   */
  this.get = injector.get;

  /**
   * Executes a function into which diagram services are injected
   *
   * @method Diagram#invoke
   *
   * @param {Function|Object[]} fn the function to resolve
   * @param {Object} locals a number of locals to use to resolve certain dependencies
   */
  this.invoke = injector.invoke;

  // init

  // indicate via event

  /**
   * An event indicating that all plug-ins are loaded.
   *
   * Use this event to fire other events to interested plug-ins
   *
   * @memberOf Diagram
   *
   * @event diagram.init
   *
   * @example
   *
   * eventBus.on('diagram.init', function() {
   *   eventBus.fire('my-custom-event', { foo: 'BAR' });
   * });
   *
   * @type {Object}
   */
  this.get('eventBus').fire('diagram.init');
}

module.exports = Diagram;

/**
 * Destroys the diagram
 *
 * @method  Diagram#destroy
 */
Diagram.prototype.destroy = function() {
  this.get('eventBus').fire('diagram.destroy');
};

/**
 * Clear the diagram, removing all contents.
 */
Diagram.prototype.clear = function() {
  this.get('eventBus').fire('diagram.clear');
};<|endoftext|>


<commit_before>$('#testing-page').live('pageinit', function(event) {

	$(document).on('pageshow', function(){
		var $carouselWrapperElement = $('.carousel-wrapper'),
			$carouselElement 		= $('.carousel');

		test( "Get Current Slide Index Test", function() {
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Swipe Left Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 1);
		});

		test( "Swipe Right Test", function() {
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Two Swipes Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Counters Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 0);
			equal( $('.position em').eq(0).hasClass('on'), true);
		});

		test( "Counter Changing Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 1);
			$carouselWrapperElement.carousel('setDotCounter', 0);
			$carouselWrapperElement.carousel('setDotCounter', 2);
			equal( $('.position em').eq(2).hasClass('on'), true);
		});

		test( "Counters Reset Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 1);
			$carouselWrapperElement.carousel('setDotCounter', 0);
			$carouselWrapperElement.carousel('setDotCounter', 2);
			$carouselWrapperElement.carousel('resetDotCounter');
			equal( $('.position em').find('on').length, 0);
		});

		test( "Orientation Change Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$('.ui-page').trigger('orientationchange');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Reset Carousel Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('resetCarousel');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});
	});
});
<commit_msg>Improved Tests<commit_after>$('#testing-page').live('pageinit', function(event) {

	$(document).on('pageshow', function(){
		var $carouselWrapperElement = $('.carousel-wrapper'),
			$carouselElement 		= $('.carousel');

		module("Basic Carousel most useful public methods");

		test( "Get Current Slide Index Test", function() {
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Swipe Left Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 1);
		});

		test( "Swipe Right Test", function() {
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Reset Carousel Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('resetCarousel');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Orientation Change Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$('.ui-page').trigger('orientationchange');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		module("Basic Carousel internal methods");

		test( "Counters Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 0);
			equal( $('.position em').eq(0).hasClass('on'), true);
		});

		test( "Counter Changing Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 1);
			$carouselWrapperElement.carousel('setDotCounter', 0);
			$carouselWrapperElement.carousel('setDotCounter', 2);
			equal( $('.position em').eq(2).hasClass('on'), true);
		});

		test( "Counters Reset Test", function() {
			$carouselWrapperElement.carousel('setDotCounter', 1);
			$carouselWrapperElement.carousel('setDotCounter', 0);
			$carouselWrapperElement.carousel('setDotCounter', 2);
			$carouselWrapperElement.carousel('resetDotCounter');
			equal( $('.position em').find('on').length, 0);
		});

		module("Basic Carousel public methods stress test");

		test( "Two Swipes Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Zig-Zag Swipes Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});

		test( "Three Swipes Test", function() {
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeLeft');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('swipeRight');
			$carouselWrapperElement.carousel('swipeRight');
			equal( $('.carousel-wrapper').carousel('getCurrentSlideIndex'), 0);
		});
	});
});
<|endoftext|>


<commit_before>/**
 * Worker to handle counting and caching tasks performed when a match is inserted or parsed.
 * All operations in this worker should deal with ephemeral data (can be reconstructed from persistent data stores)
 **/
var constants = require('../constants');
var config = require('../config');
var redis = require('../store/redis');
var queue = require('../store/queue');
var queries = require('../store/queries');
var utility = require('../util/utility');
var benchmarks = require('../util/benchmarks');
var cQueue = queue.getQueue('cache');
var moment = require('moment');
var async = require('async');
var getMatchRating = queries.getMatchRating;
cQueue.process(10, processCache);
cQueue.on('completed', function(job)
{
    job.remove();
});

function processCache(job, cb)
{
    var match = job.data.payload;
    console.log('match: %s, %s', match.match_id, match.origin);
    async.parallel(
    {
        "updateRankings": function(cb)
        {
            if (match.origin === "scanner")
            {
                return updateRankings(match, cb);
            }
            else
            {
                return cb();
            }
        },
        "updateMatchRating": function(cb)
        {
            if (match.origin === "scanner")
            {
                return updateMatchRating(match, cb);
            }
            else
            {
                return cb();
            }
        },
        "incrCounts": function(cb)
        {
            try
            {
                if (match.origin === "scanner")
                {
                    incrCounts(match);
                }
                cb();
            }
            catch (e)
            {
                return cb(e);
            }
        },
        "updateBenchmarks": function(cb)
        {
            try
            {
                if (match.origin === "scanner")
                {
                    updateBenchmarks(match);
                }
                cb();
            }
            catch (e)
            {
                return cb(e);
            }
        }
    }, function(err)
    {
        if (err)
        {
            console.error(err);
        }
        return cb(err);
    });
}

function incrCounts(match)
{
    var expire = moment().add(1, 'week').startOf('week').format('X');
    //count match for telemetry
    redis.zadd("added_match", moment().format('X'), match.match_id);
    //increment picks
    var radiant = [];
    var dire = [];
    for (var i = 0; i < match.players.length; i++)
    {
        var p = match.players[i];
        if (p.hero_id === 0)
        {
            //exclude this match
            return;
        }
        if (utility.isRadiant(p))
        {
            radiant.push(p.hero_id);
        }
        else
        {
            dire.push(p.hero_id);
        }
    }
    //compute singles, dyads, triads, etc.
    for (var i = 1; i < 4; i++)
    {
        addToPickResults(k_combinations(radiant, i), i, match.radiant_win, match);
        addToPickResults(k_combinations(dire, i), i, !match.radiant_win, match);
    }
    redis.incr('picks_match_count');
    redis.expireat('picks_match_count', expire);

    function addToPickResults(groups, i, win, m)
    {
        groups.forEach(function(g)
        {
            //sort and join the g into a unique key
            g = g.sort(function(a, b)
            {
                return a - b;
            }).join(',');
            //redis.zadd('picks:' + i + ":" + g, moment().format('X'), match.match_id);
            redis.zincrby('picks_counts:' + i, 1, g);
            redis.expireat('picks_counts:' + i, expire);
            if (win)
            {
                //redis.zadd('picks_wins:' + i + ":" + g, moment().format('X'), match.match_id);
                redis.zincrby('picks_wins_counts:' + i, 1, g);
                redis.expireat('picks_wins_counts:' + i, expire);
            }
        });
    }
}

function k_combinations(arr, k)
{
    var i, j, combs, head, tailcombs;
    if (k > arr.length || k <= 0)
    {
        return [];
    }
    if (k === arr.length)
    {
        return [arr];
    }
    if (k == 1)
    {
        combs = [];
        for (i = 0; i < arr.length; i++)
        {
            combs.push([arr[i]]);
        }
        return combs;
    }
    // Assert {1 < k < arr.length}
    combs = [];
    for (i = 0; i < arr.length - k + 1; i++)
    {
        head = arr.slice(i, i + 1);
        //recursively get all combinations of the remaining array
        tailcombs = k_combinations(arr.slice(i + 1), k - 1);
        for (j = 0; j < tailcombs.length; j++)
        {
            combs.push(head.concat(tailcombs[j]));
        }
    }
    return combs;
}

function updateRankings(match, cb)
{
    getMatchRating(redis, match, function(err, avg)
    {
        if (err)
        {
            return cb(err);
        }
        var score = (avg && !Number.isNaN(avg)) ? Math.pow(avg, 5) / 1000000000000 : undefined;
        async.each(match.players, function(player, cb)
        {
            if (!player.account_id || player.account_id === constants.anonymous_account_id)
            {
                return cb();
            }
            player.radiant_win = match.radiant_win;
            var start = moment().startOf('quarter').format('X');
            var expire = moment().add(1, 'quarter').startOf('quarter').format('X');
            var win = Number(utility.isRadiant(player) === player.radiant_win);
            if (score && utility.isSignificant(match))
            {
                console.log(match.match_id, score);
                redis.zincrby(['hero_rankings', start, player.hero_id].join(':'), win ? score : 0, player.account_id);
                redis.expireat(['hero_rankings', start, player.hero_id].join(':'), expire);
            }
            cb();
        }, cb);
    });
}

function updateBenchmarks(match)
{
    for (var i = 0; i < match.players.length; i++)
    {
        var p = match.players[i];
        if (!p.hero_id)
        {
            //exclude this match
            return;
        }
        for (var key in benchmarks)
        {
            var metric = benchmarks[key](match, p);
            if (metric !== undefined && metric !== null && !Number.isNaN(metric))
            {
                var rkey = ["benchmarks", utility.getStartOfBlockHours(config.BENCHMARK_RETENTION_HOURS, 0), key, p.hero_id].join(':');
                redis.zadd(rkey, metric, match.match_id);
                //expire at time two blocks later (after prev/current cycle)
                redis.expireat(rkey, utility.getStartOfBlockHours(config.BENCHMARK_RETENTION_HOURS, 2));
            }
        }
    }
}

function updateMatchRating(match, cb)
{
    getMatchRating(redis, match, function(err, avg)
    {
        if (avg && !Number.isNaN(avg))
        {
            var rkey = 'match_ratings:' + utility.getStartOfBlockHours(config.MATCH_RATING_RETENTION_HOURS, 0);
            redis.zadd(rkey, avg, match.match_id);
            redis.expireat(rkey, utility.getStartOfBlockHours(config.MATCH_RATING_RETENTION_HOURS, 2));
            //for each player
            match.players.forEach(function(player)
            {
                if (player.account_id && player.account_id !== constants.anonymous_account_id)
                {
                    //push into list, limit elements
                    redis.lpush('mmr_estimates:' + player.account_id, avg);
                    redis.ltrim('mmr_estimates:' + player.account_id, 0, 24);
                }
            });
            cb();
        }
        else
        {
            return cb(err);
        }
    });
}<commit_msg>Update cacher.js<commit_after>/**
 * Worker to handle counting and caching tasks performed when a match is inserted or parsed.
 * All operations in this worker should deal with ephemeral data (can be reconstructed from persistent data stores)
 **/
var constants = require('../constants');
var config = require('../config');
var redis = require('../store/redis');
var queue = require('../store/queue');
var queries = require('../store/queries');
var utility = require('../util/utility');
var benchmarks = require('../util/benchmarks');
var cQueue = queue.getQueue('cache');
var moment = require('moment');
var async = require('async');
var getMatchRating = queries.getMatchRating;
cQueue.process(10, processCache);
cQueue.on('completed', function(job)
{
    job.remove();
});

function processCache(job, cb)
{
    var match = job.data.payload;
    console.log('match: %s, %s', match.match_id, match.origin);
    async.parallel(
    {
        "updateRankings": function(cb)
        {
            if (match.origin === "scanner")
            {
                return updateRankings(match, cb);
            }
            else
            {
                return cb();
            }
        },
        "updateMatchRating": function(cb)
        {
            if (match.origin === "scanner")
            {
                return updateMatchRating(match, cb);
            }
            else
            {
                return cb();
            }
        },
        "incrCounts": function(cb)
        {
            try
            {
                if (match.origin === "scanner")
                {
                    incrCounts(match);
                }
                cb();
            }
            catch (e)
            {
                return cb(e);
            }
        },
        "updateBenchmarks": function(cb)
        {
            try
            {
                if (match.origin === "scanner")
                {
                    updateBenchmarks(match);
                }
                cb();
            }
            catch (e)
            {
                return cb(e);
            }
        }
    }, function(err)
    {
        if (err)
        {
            console.error(err);
        }
        return cb(err);
    });
}

function incrCounts(match)
{
    var expire = moment().add(1, 'week').startOf('week').format('X');
    //count match for telemetry
    redis.zadd("added_match", moment().format('X'), match.match_id);
    //increment picks
    var radiant = [];
    var dire = [];
    for (var i = 0; i < match.players.length; i++)
    {
        var p = match.players[i];
        if (p.hero_id === 0)
        {
            //exclude this match
            return;
        }
        if (utility.isRadiant(p))
        {
            radiant.push(p.hero_id);
        }
        else
        {
            dire.push(p.hero_id);
        }
    }
    //compute singles, dyads, triads, etc.
    for (var i = 1; i < 4; i++)
    {
        addToPickResults(k_combinations(radiant, i), i, match.radiant_win, match);
        addToPickResults(k_combinations(dire, i), i, !match.radiant_win, match);
    }
    redis.incr('picks_match_count');
    redis.expireat('picks_match_count', expire);

    function addToPickResults(groups, i, win, m)
    {
        groups.forEach(function(g)
        {
            //sort and join the g into a unique key
            g = g.sort(function(a, b)
            {
                return a - b;
            }).join(',');
            //redis.zadd('picks:' + i + ":" + g, moment().format('X'), match.match_id);
            redis.zincrby('picks_counts:' + i, 1, g);
            redis.expireat('picks_counts:' + i, expire);
            if (win)
            {
                //redis.zadd('picks_wins:' + i + ":" + g, moment().format('X'), match.match_id);
                redis.zincrby('picks_wins_counts:' + i, 1, g);
                redis.expireat('picks_wins_counts:' + i, expire);
            }
        });
    }
}

function k_combinations(arr, k)
{
    var i, j, combs, head, tailcombs;
    if (k > arr.length || k <= 0)
    {
        return [];
    }
    if (k === arr.length)
    {
        return [arr];
    }
    if (k == 1)
    {
        combs = [];
        for (i = 0; i < arr.length; i++)
        {
            combs.push([arr[i]]);
        }
        return combs;
    }
    // Assert {1 < k < arr.length}
    combs = [];
    for (i = 0; i < arr.length - k + 1; i++)
    {
        head = arr.slice(i, i + 1);
        //recursively get all combinations of the remaining array
        tailcombs = k_combinations(arr.slice(i + 1), k - 1);
        for (j = 0; j < tailcombs.length; j++)
        {
            combs.push(head.concat(tailcombs[j]));
        }
    }
    return combs;
}

function updateRankings(match, cb)
{
    getMatchRating(redis, match, function(err, avg)
    {
        if (err)
        {
            return cb(err);
        }
        var score = (avg && !Number.isNaN(avg)) ? Math.pow(avg, 6) / Math.pow(10, 18) : undefined;
        async.each(match.players, function(player, cb)
        {
            if (!player.account_id || player.account_id === constants.anonymous_account_id)
            {
                return cb();
            }
            player.radiant_win = match.radiant_win;
            var start = moment().startOf('quarter').format('X');
            var expire = moment().add(1, 'quarter').startOf('quarter').format('X');
            var win = Number(utility.isRadiant(player) === player.radiant_win);
            if (score && utility.isSignificant(match))
            {
                console.log(match.match_id, score);
                redis.zincrby(['hero_rankings', start, player.hero_id].join(':'), win ? score : 0, player.account_id);
                redis.expireat(['hero_rankings', start, player.hero_id].join(':'), expire);
            }
            cb();
        }, cb);
    });
}

function updateBenchmarks(match)
{
    for (var i = 0; i < match.players.length; i++)
    {
        var p = match.players[i];
        if (!p.hero_id)
        {
            //exclude this match
            return;
        }
        for (var key in benchmarks)
        {
            var metric = benchmarks[key](match, p);
            if (metric !== undefined && metric !== null && !Number.isNaN(metric))
            {
                var rkey = ["benchmarks", utility.getStartOfBlockHours(config.BENCHMARK_RETENTION_HOURS, 0), key, p.hero_id].join(':');
                redis.zadd(rkey, metric, match.match_id);
                //expire at time two blocks later (after prev/current cycle)
                redis.expireat(rkey, utility.getStartOfBlockHours(config.BENCHMARK_RETENTION_HOURS, 2));
            }
        }
    }
}

function updateMatchRating(match, cb)
{
    getMatchRating(redis, match, function(err, avg)
    {
        if (avg && !Number.isNaN(avg))
        {
            var rkey = 'match_ratings:' + utility.getStartOfBlockHours(config.MATCH_RATING_RETENTION_HOURS, 0);
            redis.zadd(rkey, avg, match.match_id);
            redis.expireat(rkey, utility.getStartOfBlockHours(config.MATCH_RATING_RETENTION_HOURS, 2));
            //for each player
            match.players.forEach(function(player)
            {
                if (player.account_id && player.account_id !== constants.anonymous_account_id)
                {
                    //push into list, limit elements
                    redis.lpush('mmr_estimates:' + player.account_id, avg);
                    redis.ltrim('mmr_estimates:' + player.account_id, 0, 24);
                }
            });
            cb();
        }
        else
        {
            return cb(err);
        }
    });
}
<|endoftext|>