<commit_before>'use strict';

let pregex = require('../lib/regex.js');

exports['pregex:match'] = function(test) {
	test.expect(22);

	// match 1
	test.equal(pregex.match('Hello World !!!', /\w+ \w+ !{3}/), true);
	test.equal(pregex.match('Hello World !!!', /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/), false);
	test.equal(pregex.match('Hello World !!!', '\\w+ \\w+ !{3}'), true);
	test.equal(pregex.match('Hello World !!!', '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$'), false);

	// match 2
	test.equal(pregex.match('test@example.com', /\w+ \w+ !{3}/), false);
	test.equal(pregex.match('test@example.com', /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/), true);
	test.equal(pregex.match('test@example.com', '\\w+ \\w+ !{3}'), false);
	test.equal(pregex.match('test@example.com', '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$'), true);

	// match 3
	let multiLines=`Multi
		foo
		bar`;
	test.equal(pregex.match(multiLines, /[a-zA-Z]*?\n(.*)/, 'i'), true);
	test.equal(pregex.match(multiLines, '[a-zA-Z]*?\\n(.*)', 'i'), true);

	// match 4
	test.equal(pregex.match('192.168.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), true);
	test.equal(pregex.match('127.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), true);
	test.equal(pregex.match('256.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), false);
	test.equal(pregex.match('255.0.0.y', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), false);
	test.equal(pregex.match('192.168.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), true);
	test.equal(pregex.match('127.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), true);
	test.equal(pregex.match('256.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), false);
	test.equal(pregex.match('255.0.0.y', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), false);

	// options
	test.equal(pregex.match('HellohelloHELLO', /(?:hello){3}/i), true);
	test.equal(pregex.match('HellohelloHELLO', '(?:hello){3}', 'i'), true);

	// error handling
	test.equal(pregex.match('test', '(test'), false);
	test.equal(pregex.match('test', 'test', 'e'), false);

	test.done();
};

exports['pregex:exec'] = function(test) {
	test.expect(27);

	// exec 1
	test.deepEqual(pregex.exec('Hello World !!!', /\d+/), null);
	test.deepEqual(pregex.exec('Hello World !!!', /\w+/), ['Hello']);
	test.deepEqual(pregex.exec('Hello World !!!', /\w+ (\w+)/), ['Hello World', 'World']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\d+'), null);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+'), ['Hello']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+ (\\w+)'), ['Hello World', 'World']);

	// exec 2
	test.deepEqual(pregex.exec('My email is testexample.com ^_^', /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), null);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), ['test@example.com']);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', /([a-zA-Z0-9_.+-]+)@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), ['test@example.com', 'test']);
	test.deepEqual(pregex.exec('My email is testexample.com ^_^', '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), null);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), ['test@example.com']);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', '([a-zA-Z0-9_.+-]+)@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), ['test@example.com', 'test']);

	// exec 3
	let multiLines=`Multi
		foo
		bar`;
	test.deepEqual(pregex.exec(multiLines, /[a-zA-Z]*?\n(.*)/, 'i'), ["Multi\n\t\tfoo\n\t\tbar", "\t\tfoo\n\t\tbar"]);
	test.deepEqual(pregex.exec(multiLines, '[a-zA-Z]*?\\n(.*)', 'i'), ["Multi\n\t\tfoo\n\t\tbar", "\t\tfoo\n\t\tbar"]);

	// exec 4
	test.deepEqual(pregex.exec('192.168.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), ['192.168.0.1', '192', '168', '0', '1']);
	test.deepEqual(pregex.exec('127.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), ['127.0.0.1', '127', '0', '0', '1']);
	test.deepEqual(pregex.exec('256.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), null);
	test.deepEqual(pregex.exec('255.0.0.y', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), null);
	test.deepEqual(pregex.exec('192.168.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), ['192.168.0.1', '192', '168', '0', '1']);
	test.deepEqual(pregex.exec('127.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), ['127.0.0.1', '127', '0', '0', '1']);
	test.deepEqual(pregex.exec('256.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), null);
	test.deepEqual(pregex.exec('255.0.0.y', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), null);

	// options 1
	test.deepEqual(pregex.exec('Hello World !!!', /\w+/g), ['World', 'Hello', 'World']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+', 'g'), ['World', 'Hello', 'World']);

	// options 2
	test.deepEqual(pregex.exec("tag list:\n\tabc\n\tdef", '^tag[^\\n]*\\n(.*)', 'ms'), ["tag list:\n\tabc\n\tdef", "\tabc\n\tdef"]);

	// error handling
	test.equal(pregex.exec('test', '(test'), null);
	test.equal(pregex.exec('test', 'test', 'e'), null);

	test.done();
};
<commit_msg>- Fix testcases regex bug<commit_after>'use strict';

let pregex = require('../lib/regex.js');

exports['pregex:match'] = function(test) {
	test.expect(22);

	// match 1
	test.equal(pregex.match('Hello World !!!', /\w+ \w+ !{3}/), true);
	test.equal(pregex.match('Hello World !!!', /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/), false);
	test.equal(pregex.match('Hello World !!!', '\\w+ \\w+ !{3}'), true);
	test.equal(pregex.match('Hello World !!!', '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$'), false);

	// match 2
	test.equal(pregex.match('test@example.com', /\w+ \w+ !{3}/), false);
	test.equal(pregex.match('test@example.com', /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/), true);
	test.equal(pregex.match('test@example.com', '\\w+ \\w+ !{3}'), false);
	test.equal(pregex.match('test@example.com', '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$'), true);

	// match 3
	let multiLines=`Multi
		foo
		bar`;
	test.equal(pregex.match(multiLines, /[a-z]*?\n(.*)/i), true);
	test.equal(pregex.match(multiLines, '[a-z]*?\\n(.*)', 'i'), true);

	// match 4
	test.equal(pregex.match('192.168.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), true);
	test.equal(pregex.match('127.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), true);
	test.equal(pregex.match('256.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), false);
	test.equal(pregex.match('255.0.0.y', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), false);
	test.equal(pregex.match('192.168.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), true);
	test.equal(pregex.match('127.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), true);
	test.equal(pregex.match('256.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), false);
	test.equal(pregex.match('255.0.0.y', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), false);

	// options
	test.equal(pregex.match('HellohelloHELLO', /(?:hello){3}/i), true);
	test.equal(pregex.match('HellohelloHELLO', '(?:hello){3}', 'i'), true);

	// error handling
	test.equal(pregex.match('test', '(test'), false);
	test.equal(pregex.match('test', 'test', 'e'), false);

	test.done();
};

exports['pregex:exec'] = function(test) {
	test.expect(27);

	// exec 1
	test.deepEqual(pregex.exec('Hello World !!!', /\d+/), null);
	test.deepEqual(pregex.exec('Hello World !!!', /\w+/), ['Hello']);
	test.deepEqual(pregex.exec('Hello World !!!', /\w+ (\w+)/), ['Hello World', 'World']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\d+'), null);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+'), ['Hello']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+ (\\w+)'), ['Hello World', 'World']);

	// exec 2
	test.deepEqual(pregex.exec('My email is testexample.com ^_^', /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), null);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), ['test@example.com']);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', /([a-zA-Z0-9_.+-]+)@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/), ['test@example.com', 'test']);
	test.deepEqual(pregex.exec('My email is testexample.com ^_^', '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), null);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), ['test@example.com']);
	test.deepEqual(pregex.exec('My email is test@example.com ^_^', '([a-zA-Z0-9_.+-]+)@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+'), ['test@example.com', 'test']);

	// exec 3
	let multiLines=`Multi
		foo
		bar`;
	test.deepEqual(pregex.exec(multiLines, /[a-z]*?\n(.*)/i), ["Multi\n\t\tfoo\n\t\tbar", "\t\tfoo\n\t\tbar"]);
	test.deepEqual(pregex.exec(multiLines, '[a-z]*?\\n(.*)', 'i'), ["Multi\n\t\tfoo\n\t\tbar", "\t\tfoo\n\t\tbar"]);

	// exec 4
	test.deepEqual(pregex.exec('192.168.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), ['192.168.0.1', '192', '168', '0', '1']);
	test.deepEqual(pregex.exec('127.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), ['127.0.0.1', '127', '0', '0', '1']);
	test.deepEqual(pregex.exec('256.0.0.1', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), null);
	test.deepEqual(pregex.exec('255.0.0.y', /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/), null);
	test.deepEqual(pregex.exec('192.168.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), ['192.168.0.1', '192', '168', '0', '1']);
	test.deepEqual(pregex.exec('127.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), ['127.0.0.1', '127', '0', '0', '1']);
	test.deepEqual(pregex.exec('256.0.0.1', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), null);
	test.deepEqual(pregex.exec('255.0.0.y', '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'), null);

	// options 1
	test.deepEqual(pregex.exec('Hello World !!!', /\w+/g), ['World', 'Hello', 'World']);
	test.deepEqual(pregex.exec('Hello World !!!', '\\w+', 'g'), ['World', 'Hello', 'World']);

	// options 2
	test.deepEqual(pregex.exec("tag list:\n\tabc\n\tdef", '^tag[^\\n]*\\n(.*)', 'ms'), ["tag list:\n\tabc\n\tdef", "\tabc\n\tdef"]);

	// error handling
	test.equal(pregex.exec('test', '(test'), null);
	test.equal(pregex.exec('test', 'test', 'e'), null);

	test.done();
};
<|endoftext|>


<commit_before>const _ = require('underscore');
import classNames from 'classnames';
import {List, Tooltip} from 'reactjs-components';
const React = require('react');

import Config from '../config/Config';
const HealthLabels = require('../constants/HealthLabels');
const HealthStatus = require('../constants/HealthStatus');
const HealthTypesDescription = require('../constants/HealthTypesDescription');
const MarathonStore = require('../stores/MarathonStore');

let ServiceList = React.createClass({

  displayName: 'ServiceList',

  propTypes: {
    services: React.PropTypes.array.isRequired,
    healthProcessed: React.PropTypes.bool.isRequired
  },

  contextTypes: {
    router: React.PropTypes.func
  },

  getDefaultProps: function () {
    return {
      services: []
    };
  },

  shouldComponentUpdate: function (nextProps, nextState) {
    var changedState =
      nextState !== undefined && !_.isEqual(this.state, nextState);

    return !_.isEqual(this.props, nextProps) || changedState;
  },

  handleServiceClick: function (serviceName) {
    this.context.router.transitionTo('dashboard-panel', {serviceName});
  },

  getServices: function (services, healthProcessed) {
    return _.map(services, function (service) {
      let appHealth = MarathonStore.getServiceHealth(service.name);
      let state = HealthStatus.NA;
      let tooltipContent;

      if (appHealth != null) {
        state = HealthStatus[appHealth.key];

        if (appHealth.key === HealthStatus.HEALTHY.key) {
          tooltipContent = HealthTypesDescription.HEALTHY;
        } else if (appHealth.key === HealthStatus.UNHEALTHY.key) {
          tooltipContent = HealthTypesDescription.UNHEALTHY;
        } else if (appHealth.key === HealthStatus.IDLE.key) {
          tooltipContent = HealthTypesDescription.IDLE;
        } else if (appHealth.key === HealthStatus.NA.key) {
          tooltipContent = HealthTypesDescription.NA;
        }
      }

      let healthLabel = HealthLabels[state.key];
      if (!healthProcessed) {
        healthLabel = (

        );
      }

      let classSet = classNames(
        'tooltip-wrapper h4 inverse flush-top flush-bottom',
        state.classNames
      );

      return {
        content: [
          {
            className: 'text-overflow',
            content: (

                {service.name}

            ),
            tag: 'span'
          },
          {
            className: 'service-list-component-health-label text-align-right',
            content: (
              <Tooltip anchor="end" content={tooltipContent} key="health"
                wrapperClassName={classSet} wrapText={true} width={200}>
                {healthLabel}
              </Tooltip>
            ),
            tag: 'div'
          }
        ]
      };
    }, this);
  },

  getNoServicesMessage: function () {
    return (

No Services Running

Use the {Config.productName} command line tools to find and install services.

    );
  },

  getList: function () {
    let props = this.props;

    return (

        <List
          className="list list-unstyled"
          content={this.getServices(props.services, props.healthProcessed)}
          transition={false} />

    );
  },

  getContent: function () {
    if (this.props.services.length === 0) {
      return this.getNoServicesMessage();
    } else {
      return this.getList();
    }
  },

  render: function () {
    return (

        {this.getContent()}

    );
  }
});

module.exports = ServiceList;
<commit_msg>Open service ui with modifier + mouse click<commit_after>const _ = require('underscore');
import classNames from 'classnames';
import {List, Tooltip} from 'reactjs-components';
const React = require('react');

import Cluster from '../utils/Cluster';
import Config from '../config/Config';
const HealthLabels = require('../constants/HealthLabels');
const HealthStatus = require('../constants/HealthStatus');
const HealthTypesDescription = require('../constants/HealthTypesDescription');
const MarathonStore = require('../stores/MarathonStore');

let ServiceList = React.createClass({

  displayName: 'ServiceList',

  propTypes: {
    services: React.PropTypes.array.isRequired,
    healthProcessed: React.PropTypes.bool.isRequired
  },

  contextTypes: {
    router: React.PropTypes.func
  },

  getDefaultProps: function () {
    return {
      services: []
    };
  },

  shouldComponentUpdate: function (nextProps, nextState) {
    var changedState =
      nextState !== undefined && !_.isEqual(this.state, nextState);

    return !_.isEqual(this.props, nextProps) || changedState;
  },

  handleServiceClick: function (service, event) {
    // Open service in new window/tab if service has a web URL
    if (service.webui_url &&
      (event.ctrlKey || event.shiftKey || event.metaKey)) {
      return;
    }

    // Modifier key not pressed or service didn't have a web URL, open detail
    event.preventDefault();
    this.context.router.transitionTo(
      'dashboard-panel',
      {serviceName: service.name}
    );
  },

  getServices: function (services, healthProcessed) {
    return _.map(services, function (service) {
      let appHealth = MarathonStore.getServiceHealth(service.name);
      let state = HealthStatus.NA;
      let tooltipContent;

      if (appHealth != null) {
        state = HealthStatus[appHealth.key];

        if (appHealth.key === HealthStatus.HEALTHY.key) {
          tooltipContent = HealthTypesDescription.HEALTHY;
        } else if (appHealth.key === HealthStatus.UNHEALTHY.key) {
          tooltipContent = HealthTypesDescription.UNHEALTHY;
        } else if (appHealth.key === HealthStatus.IDLE.key) {
          tooltipContent = HealthTypesDescription.IDLE;
        } else if (appHealth.key === HealthStatus.NA.key) {
          tooltipContent = HealthTypesDescription.NA;
        }
      }

      let healthLabel = HealthLabels[state.key];
      if (!healthProcessed) {
        healthLabel = (

        );
      }

      let classSet = classNames(
        'tooltip-wrapper h4 inverse flush-top flush-bottom',
        state.classNames
      );

      return {
        content: [
          {
            className: 'text-overflow',
            content: (

                {service.name}

            ),
            tag: 'span'
          },
          {
            className: 'service-list-component-health-label text-align-right',
            content: (
              <Tooltip anchor="end" content={tooltipContent} key="health"
                wrapperClassName={classSet} wrapText={true} width={200}>
                {healthLabel}
              </Tooltip>
            ),
            tag: 'div'
          }
        ]
      };
    }, this);
  },

  getNoServicesMessage: function () {
    return (

No Services Running

Use the {Config.productName} command line tools to find and install services.

    );
  },

  getList: function () {
    let props = this.props;

    return (

        <List
          className="list list-unstyled"
          content={this.getServices(props.services, props.healthProcessed)}
          transition={false} />

    );
  },

  getContent: function () {
    if (this.props.services.length === 0) {
      return this.getNoServicesMessage();
    } else {
      return this.getList();
    }
  },

  render: function () {
    return (

        {this.getContent()}

    );
  }
});

module.exports = ServiceList;
<|endoftext|>


<commit_before>var chai = require('chai');
var sinon = require('sinon');
var child = require('child_process');
var exec = child.exec;
var fs = require('fs');
var rewire = require('rewire');
var suitcss = rewire('../lib');
var path = require('path');
var sinonChai = require('sinon-chai');
require('sinon-as-promised');

chai.use(sinonChai);
var expect = chai.expect;

/**
 * Node API tests.
 */

describe('suitcss', function() {
  it('should return a css string', function(done) {
    suitcss('body {}').then(function(result) {
      expect(result.css).to.be.a('string');
      done();
    });
  });

  it('should handle invalid input', function() {
    expect(function() {suitcss(null);}).to.throw(TypeError);
  });

  describe('using options', function() {
    var mergeOptions, defaults;

    beforeEach(function() {
      mergeOptions = suitcss.__get__('mergeOptions');
      defaults = suitcss.__get__('defaults');
    });

    it('should use default options when nothing is passed', function() {
      var keys = [
        'minify',
        'use',
        'lint',
        'postcss-import',
        'postcss-reporter',
        'cssnano'
      ];
      expect(mergeOptions({})).to.have.keys(keys);
      expect(mergeOptions()).to.have.keys(keys);
      expect(mergeOptions({}).use).to.eql(defaults.use);
      expect(mergeOptions().use).to.eql(defaults.use);
    });

    it('should allow an import root to be set', function() {
      var opts = mergeOptions({root: 'test/root'});
      expect(opts['postcss-import'].root).to.equal('test/root');
    });

    it('should allow stylelint to be enabled', function() {
      var opts = mergeOptions({lint: true});
      expect(opts.lint).to.be.true;
    });

    it('should allow a minify option to be set', function() {
      var opts = mergeOptions({minify: true});
      expect(opts.minify).to.be.true;
    });

    it('should merge config options with existing defaults', function() {
      var autoprefixer = {browsers: ['> 1%', 'IE 7'], cascade: false};
      var opts = mergeOptions({
        root: 'test/root',
        autoprefixer: autoprefixer
      });

      expect(opts.use).to.eql([
        'postcss-import',
        'postcss-custom-properties',
        'postcss-calc',
        'postcss-custom-media',
        'autoprefixer',
        'postcss-reporter'
      ]);
      expect(opts.autoprefixer).to.eql(autoprefixer);
      expect(opts['postcss-import'].root).to.equal('test/root');
    });

    describe('passing options to postcss', function() {
      var postcssStub, processMethodStub, revert;

      beforeEach(function() {
        postcssStub = sinon.stub();
        processMethodStub = sinon.stub();

        postcssStub.returns({
          use: sinon.spy(),
          process: processMethodStub
        });
        revert = suitcss.__set__('postcss', postcssStub);
        suitcss('body {}', {
          root: 'something',
          postcss: {
            from: 'somefile.css'
          }
        });
      });

      afterEach(function() {
        revert();
      });

      it('should pass postcss options to the processor', function() {
        expect(processMethodStub.getCall(0).args[1]).to.eql({
          from: 'somefile.css'
        });
      });
    });

    describe('re-ordering plugins', function() {
      it('should allow reordering of use array and remove duplicates', function() {
        var opts = mergeOptions({
          use: ['autoprefixer', 'postcss-at2x', 'postcss-calc', 'postcss-reporter']
        });

        expect(opts.use).to.eql([
          'postcss-import',
          'postcss-custom-properties',
          'postcss-custom-media',
          'autoprefixer',
          'postcss-at2x',
          'postcss-calc',
          'postcss-reporter'
        ]);
      });

      it('should just append plugins if no duplicates are used', function() {
        var opts = mergeOptions({
          use: ['postcss-at2x', 'postcss-property-lookup']
        });

        expect(opts.use).to.eql([
          'postcss-import',
          'postcss-custom-properties',
          'postcss-calc',
          'postcss-custom-media',
          'autoprefixer',
          'postcss-reporter',
          'postcss-at2x',
          'postcss-property-lookup'
        ]);
      });
    });

    describe('stylelint', function() {
      it('should check the input conforms to SUIT style rules', function(done) {
        suitcss('@import "./stylelint.css"', {
          lint: true,
          root: 'test/fixtures',
          'postcss-reporter': {
            throwError: true
          }
        }).then(function() {
          done(new Error('stylelint should have failed conformance'));
        }).catch(function(err) {
          expect(err.message).to.contain('postcss-reporter: warnings or errors were found');
          done();
        });
      });
    });

    describe('transforming css before linting', function() {
      var lintImportedFilesStub, beforeLintStub, revert;

      beforeEach(function() {
        var postcssPromise = sinon.stub().resolves('/*linting done*/')();
        lintImportedFilesStub = sinon.stub().returns(postcssPromise);
        beforeLintStub = sinon.stub().returns('/*before lint*/');
        revert = suitcss.__set__('lintImportedFiles', lintImportedFilesStub);
      });

      afterEach(function() {
        revert();
      });

      it('should call `beforeLint` function before linting', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(beforeLintStub).to.be.calledOnce;
          expect(lintImportedFilesStub).to.be.calledOnce;
          expect(beforeLintStub).to.have.been.calledBefore(lintImportedFilesStub);
          done();
        })
        .catch(done);
      });

      it('should pass the result of `beforeLint` to `lintImportedFiles`', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(lintImportedFilesStub.args[0][1]).to.equal('/*before lint*/');
          done();
        })
        .catch(done);
      });

      it('should pass the options object to the beforeLint function as the third parameter', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(beforeLintStub.args[0][2]).to.contain({root: 'test/fixtures'});
          done();
        })
        .catch(done);
      });
    });
  });
});

/**
 * Feature tests.
 */

describe('features', function() {
  it('should preprocess CSS correctly', function(done) {
    var input = read('fixtures/component');
    var output = read('fixtures/component.out');

    suitcss(input, {root: 'test/fixtures'}).then(function(result) {
      expect(result.css.trim()).to.be.equal(output.trim());
      done();
    }).catch(done);
  });
});

/**
 * CLI tests.
 */

describe('cli', function() {
  var input = read('fixtures/cli/input');
  var output = read('fixtures/cli/input.out');

  afterEach(function() {
    remove('fixtures/cli/output');
  });

  it('should read from a file and write to a file', function(done) {
    exec('bin/suitcss test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      expect(res).to.equal(output);
      done();
    });
  });

  it('should read from a file and write to stdout', function(done) {
    exec('bin/suitcss test/fixtures/cli/input.css', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.equal(output);
      done();
    });
  });

  it('should read from stdin and write to stdout', function(done) {
    var testChild = exec('bin/suitcss', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.equal(output);
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });

    testChild.stdin.write(new Buffer(input));
    testChild.stdin.end();
  });

  it('should log on verbose', function(done) {
    exec('bin/suitcss -v test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.contain('write');
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });
  });

  it('should allow configurable import root', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/import.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/component.out');
      expect(res).to.equal(expected);
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });
  });

  it('should output stylelint warnings', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/stylelint-import.css test/fixtures/cli/output.css -l', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.contain('Expected property "box-sizing" to come before property "flex"');
      done();
    });
  });

  it('should minify the output', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/import.css test/fixtures/cli/output.css -m', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/minify.out');
      expect(stdout).to.not.contain('beforeLint ran');
      expect(res).to.equal(expected);
      done();
    });
  });

  it('should allow a config file to be passed', function(done) {
    exec('bin/suitcss -i test/fixtures -c test/test.config.js test/fixtures/config.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/config.out');
      expect(stdout).to.contain('beforeLint ran');
      expect(res).to.equal(expected);
      done();
    });
  });

  it('should output an error to stderr on conformance failure when throwError is set', function(done) {
    exec('bin/suitcss -i test/fixtures -c test/error.config.json test/fixtures/import-error.css test/fixtures/cli/output.css', function(err, stdout, stderr) {
      expect(err).to.be.an('error');
      expect(err.code).to.equal(1);
      expect(stderr).to.contain('postcss-reporter: warnings or errors were found');
      done();
    });
  });

  it('should log on non-existant file', function(done) {
    exec('bin/suitcss test/fixtures/cli/non-existant.css', function(err, stdout, stderr) {
      expect(err).to.be.an('error');
      expect(err.code).to.equal(1);
      expect(stderr).to.contain('not found');
      done();
    });
  });
});

/**
 * Read a fixture by `filename`.
 *
 * @param {String} filename
 * @return {String}
 */

function read(filename) {
  var file = resolve(filename);
  return fs.readFileSync(file, 'utf8');
}

/**
 * Remove a fixture by `filename`.
 *
 * @param {String} filename
 */

function remove(filename) {
  var file = resolve(filename);
  if (!fs.existsSync(file)) return;
  fs.unlinkSync(file);
}

/**
 * Resolve a fixture by `filename`.
 *
 * @param {String} filename
 * @return {String}
 */

function resolve(filename) {
  return path.resolve(__dirname, filename + '.css');
}
<commit_msg>Improved test name<commit_after>var chai = require('chai');
var sinon = require('sinon');
var child = require('child_process');
var exec = child.exec;
var fs = require('fs');
var rewire = require('rewire');
var suitcss = rewire('../lib');
var path = require('path');
var sinonChai = require('sinon-chai');
require('sinon-as-promised');

chai.use(sinonChai);
var expect = chai.expect;

/**
 * Node API tests.
 */

describe('suitcss', function() {
  it('should return a css string', function(done) {
    suitcss('body {}').then(function(result) {
      expect(result.css).to.be.a('string');
      done();
    });
  });

  it('should handle invalid input', function() {
    expect(function() {suitcss(null);}).to.throw(TypeError);
  });

  describe('using options', function() {
    var mergeOptions, defaults;

    beforeEach(function() {
      mergeOptions = suitcss.__get__('mergeOptions');
      defaults = suitcss.__get__('defaults');
    });

    it('should use default options when nothing is passed', function() {
      var keys = [
        'minify',
        'use',
        'lint',
        'postcss-import',
        'postcss-reporter',
        'cssnano'
      ];
      expect(mergeOptions({})).to.have.keys(keys);
      expect(mergeOptions()).to.have.keys(keys);
      expect(mergeOptions({}).use).to.eql(defaults.use);
      expect(mergeOptions().use).to.eql(defaults.use);
    });

    it('should allow an import root to be set', function() {
      var opts = mergeOptions({root: 'test/root'});
      expect(opts['postcss-import'].root).to.equal('test/root');
    });

    it('should allow stylelint to be enabled', function() {
      var opts = mergeOptions({lint: true});
      expect(opts.lint).to.be.true;
    });

    it('should allow a minify option to be set', function() {
      var opts = mergeOptions({minify: true});
      expect(opts.minify).to.be.true;
    });

    it('should merge config options with existing defaults', function() {
      var autoprefixer = {browsers: ['> 1%', 'IE 7'], cascade: false};
      var opts = mergeOptions({
        root: 'test/root',
        autoprefixer: autoprefixer
      });

      expect(opts.use).to.eql([
        'postcss-import',
        'postcss-custom-properties',
        'postcss-calc',
        'postcss-custom-media',
        'autoprefixer',
        'postcss-reporter'
      ]);
      expect(opts.autoprefixer).to.eql(autoprefixer);
      expect(opts['postcss-import'].root).to.equal('test/root');
    });

    describe('passing options to postcss', function() {
      var postcssStub, processMethodStub, revert;

      beforeEach(function() {
        postcssStub = sinon.stub();
        processMethodStub = sinon.stub();

        postcssStub.returns({
          use: sinon.spy(),
          process: processMethodStub
        });
        revert = suitcss.__set__('postcss', postcssStub);
        suitcss('body {}', {
          root: 'something',
          postcss: {
            from: 'somefile.css'
          }
        });
      });

      afterEach(function() {
        revert();
      });

      it('should pass postcss options to the processor', function() {
        expect(processMethodStub.getCall(0).args[1]).to.eql({
          from: 'somefile.css'
        });
      });
    });

    describe('re-ordering plugins', function() {
      it('should allow reordering of use array and remove duplicates', function() {
        var opts = mergeOptions({
          use: ['autoprefixer', 'postcss-at2x', 'postcss-calc', 'postcss-reporter']
        });

        expect(opts.use).to.eql([
          'postcss-import',
          'postcss-custom-properties',
          'postcss-custom-media',
          'autoprefixer',
          'postcss-at2x',
          'postcss-calc',
          'postcss-reporter'
        ]);
      });

      it('should just append plugins if no duplicates are used', function() {
        var opts = mergeOptions({
          use: ['postcss-at2x', 'postcss-property-lookup']
        });

        expect(opts.use).to.eql([
          'postcss-import',
          'postcss-custom-properties',
          'postcss-calc',
          'postcss-custom-media',
          'autoprefixer',
          'postcss-reporter',
          'postcss-at2x',
          'postcss-property-lookup'
        ]);
      });
    });

    describe('stylelint', function() {
      it('should throw an error if stylelint fails', function(done) {
        suitcss('@import "./stylelint.css"', {
          lint: true,
          root: 'test/fixtures',
          'postcss-reporter': {
            throwError: true
          }
        }).then(function() {
          done(new Error('stylelint should have failed conformance'));
        }).catch(function(err) {
          expect(err.message).to.contain('postcss-reporter: warnings or errors were found');
          done();
        });
      });
    });

    describe('transforming css before linting', function() {
      var lintImportedFilesStub, beforeLintStub, revert;

      beforeEach(function() {
        var postcssPromise = sinon.stub().resolves('/*linting done*/')();
        lintImportedFilesStub = sinon.stub().returns(postcssPromise);
        beforeLintStub = sinon.stub().returns('/*before lint*/');
        revert = suitcss.__set__('lintImportedFiles', lintImportedFilesStub);
      });

      afterEach(function() {
        revert();
      });

      it('should call `beforeLint` function before linting', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(beforeLintStub).to.be.calledOnce;
          expect(lintImportedFilesStub).to.be.calledOnce;
          expect(beforeLintStub).to.have.been.calledBefore(lintImportedFilesStub);
          done();
        })
        .catch(done);
      });

      it('should pass the result of `beforeLint` to `lintImportedFiles`', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(lintImportedFilesStub.args[0][1]).to.equal('/*before lint*/');
          done();
        })
        .catch(done);
      });

      it('should pass the options object to the beforeLint function as the third parameter', function(done) {
        suitcss(read('fixtures/component'), {
          root: 'test/fixtures',
          beforeLint: beforeLintStub
        })
        .then(function() {
          expect(beforeLintStub.args[0][2]).to.contain({root: 'test/fixtures'});
          done();
        })
        .catch(done);
      });
    });
  });
});

/**
 * Feature tests.
 */

describe('features', function() {
  it('should preprocess CSS correctly', function(done) {
    var input = read('fixtures/component');
    var output = read('fixtures/component.out');

    suitcss(input, {root: 'test/fixtures'}).then(function(result) {
      expect(result.css.trim()).to.be.equal(output.trim());
      done();
    }).catch(done);
  });
});

/**
 * CLI tests.
 */

describe('cli', function() {
  var input = read('fixtures/cli/input');
  var output = read('fixtures/cli/input.out');

  afterEach(function() {
    remove('fixtures/cli/output');
  });

  it('should read from a file and write to a file', function(done) {
    exec('bin/suitcss test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      expect(res).to.equal(output);
      done();
    });
  });

  it('should read from a file and write to stdout', function(done) {
    exec('bin/suitcss test/fixtures/cli/input.css', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.equal(output);
      done();
    });
  });

  it('should read from stdin and write to stdout', function(done) {
    var testChild = exec('bin/suitcss', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.equal(output);
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });

    testChild.stdin.write(new Buffer(input));
    testChild.stdin.end();
  });

  it('should log on verbose', function(done) {
    exec('bin/suitcss -v test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.contain('write');
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });
  });

  it('should allow configurable import root', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/import.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/component.out');
      expect(res).to.equal(expected);
      expect(stdout).to.not.contain('beforeLint ran');
      done();
    });
  });

  it('should output stylelint warnings', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/stylelint-import.css test/fixtures/cli/output.css -l', function(err, stdout) {
      if (err) return done(err);
      expect(stdout).to.contain('Expected property "box-sizing" to come before property "flex"');
      done();
    });
  });

  it('should minify the output', function(done) {
    exec('bin/suitcss -i test/fixtures test/fixtures/import.css test/fixtures/cli/output.css -m', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/minify.out');
      expect(stdout).to.not.contain('beforeLint ran');
      expect(res).to.equal(expected);
      done();
    });
  });

  it('should allow a config file to be passed', function(done) {
    exec('bin/suitcss -i test/fixtures -c test/test.config.js test/fixtures/config.css test/fixtures/cli/output.css', function(err, stdout) {
      if (err) return done(err);
      var res = read('fixtures/cli/output');
      var expected = read('fixtures/config.out');
      expect(stdout).to.contain('beforeLint ran');
      expect(res).to.equal(expected);
      done();
    });
  });

  it('should output an error to stderr on conformance failure when throwError is set', function(done) {
    exec('bin/suitcss -i test/fixtures -c test/error.config.json test/fixtures/import-error.css test/fixtures/cli/output.css', function(err, stdout, stderr) {
      expect(err).to.be.an('error');
      expect(err.code).to.equal(1);
      expect(stderr).to.contain('postcss-reporter: warnings or errors were found');
      done();
    });
  });

  it('should log on non-existant file', function(done) {
    exec('bin/suitcss test/fixtures/cli/non-existant.css', function(err, stdout, stderr) {
      expect(err).to.be.an('error');
      expect(err.code).to.equal(1);
      expect(stderr).to.contain('not found');
      done();
    });
  });
});

/**
 * Read a fixture by `filename`.
 *
 * @param {String} filename
 * @return {String}
 */

function read(filename) {
  var file = resolve(filename);
  return fs.readFileSync(file, 'utf8');
}

/**
 * Remove a fixture by `filename`.
 *
 * @param {String} filename
 */

function remove(filename) {
  var file = resolve(filename);
  if (!fs.existsSync(file)) return;
  fs.unlinkSync(file);
}

/**
 * Resolve a fixture by `filename`.
 *
 * @param {String} filename
 * @return {String}
 */

function resolve(filename) {
  return path.resolve(__dirname, filename + '.css');
}
<|endoftext|>


<commit_before>var app = angular.module("geonote", []);

app.factory("urlTools", function (){
	"use strict";

	function getURL(name, parameters) {
		var url = name;

		var i = 0;

		angular.forEach(parameters, function(key, value) {
			if (i == 0)
				url += ('?' + value + '=' + key)
			else
				url += ('&' + value + '=' + key)
			i=i+1
		});

		return url;
	}

	return {
		getURL: getURL,
	};
});

app.service("notePoller", function($timeout, $http, urlTools) {
	var notes = [];
	var polling = false;
	var lastPollTime;

	var poller = {
		startPolling: function() {
			polling = true;
			poll();
		},

		stopPolling: function() {
			polling = false;
		},

		getNotes: function() {
			return notes;
		},

		createNote: function(noteMsg, lat, lng) {
			var url = urlTools.getURL('/create_note/', {
				content: noteMsg,
				lat: lat,
				lng: lng
			});

			$.ajax({
		  		url: url,
		  		success: successCreate,
		  		dataType: 'json'
			});
		}
	}

	function poll() {
		if (!polling) return;

		if (!lastPollTime) {
			console.log("1");
			lastPollTime = Math.round(new Date().getTime() / 1000);

			var url = urlTools.getURL('/get_notes', {
				ne_lat: 5.0,
				ne_long: 5.0,
				sw_lat: 5.0,
				sw_long: 5.0
			});

			$.ajax({
			  type:"get",
			  url: url,
			  success: successPoll,
			  dataType: 'json'
			});
		} else {
			console.log(lastPollTime);
			var url = urlTools.getURL('/get_recent_notes', {
				ne_lat: 5.0,
				ne_long: 5.0,
				sw_lat: 5.0,
				sw_long: 5.0,
				timestamp: lastPollTime
			});

			lastPollTime = Math.round(new Date().getTime() / 1000);

			$.ajax({
			  type:"get",
			  url: url,
			  success: successPoll,
			  dataType: 'json'
			});
		}

		$timeout(poll, 5000);
	}

	function successPoll(data) {
		console.log(data);
		notes = data;
	}

	function successCreate(data) {

	}

	return poller;
})

app.service("geoLocation", function() {
	var lat = "32.879996";
	var lng = "-117.237046";
	var locationReady = false;

	var locationServices = {
		computeLocation: function() {
			if (navigator.geolocation) {
		        navigator.geolocation.getCurrentPosition(setPosition);
		    } else {
		    	//set location to ucsd center
		    }
		},

		getLocation: function() {
			return [lat, lng];
		},

		locationReady: function() {
			return locationReady;
		}
	}

	function setPosition(position) {
		lat = position.coords.latitude;
		lng = position.coords.longitude;
		locationReady = true;
	}

	return locationServices;
});

app.directive("mapHandler", function() {
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, $rootScope, notePoller, geoLocation, $timeout) {
			$scope.currPos = ["32.879996", "-117.237046"];
			$scope.notes = [];

			geoLocation.computeLocation();

			$timeout(updateAll, 2000);

			$scope.map = L.map('map').setView($scope.currPos, 15);

			L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
				maxZoom: 18,
				id: 'examples.map-i875mjb7'
			}).addTo($scope.map);

			var currLocationMarker = L.marker($scope.currPos).addTo($scope.map);

			var markers = L.markerClusterGroup();

			notePoller.startPolling();

			function updateLocation() {
				currLocationMarker.setLatLng($scope.currPos);
			}

			function updateAll() {
				if (geoLocation.locationReady()) {
					$scope.currPos = geoLocation.getLocation();
					updateLocation();
				}
				$scope.notes = notePoller.getNotes();

				angular.forEach($scope.notes, function(note) {
					 var redMarker = L.AwesomeMarkers.icon({
					    icon: 'cutlery',
					    markerColor: 'red'
					  });

					  var marker = L.marker([note.lat, note.lng], {icon: redMarker}).addTo($scope.map);
					  marker.bindPopup(note.content);
					  markers.addLayer(marker);
				});
				$scope.map.addLayer(markers);
				$timeout(updateAll, 5100);
			}
		}
	}
});

app.directive("viewNotesInterface", function() {
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, notePoller, geoLocation) {
			$scope.notes = [];

			$scope.$watch(notePoller.getNotes(), function() {

			})
		}
	}
});

app.directive("createNoteInterface", function() {
	/*
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, notePoller, geoLocation) {
			$scope.notes = []

			$timeout(updateAll, 2000);
		}
	}*/
});

app.controller("GeonoteController", [ "$scope", 'notePoller', 'geoLocation', function($scope, notePoller, geoLocation) {

}]);
<commit_msg>fixed angular bullshit<commit_after>var app = angular.module("geonote", []);

app.factory("urlTools", function (){
	"use strict";

	function getURL(name, parameters) {
		var url = name;

		var i = 0;

		angular.forEach(parameters, function(key, value) {
			if (i == 0)
				url += ('?' + value + '=' + key)
			else
				url += ('&' + value + '=' + key)
			i=i+1
		});

		return url;
	}

	return {
		getURL: getURL,
	};
});

app.service("notePoller", function($timeout, $http, urlTools) {
	var notes = [];
	var polling = false;
	var lastPollTime;

	var poller = {
		startPolling: function() {
			polling = true;
			poll();
		},

		stopPolling: function() {
			polling = false;
		},

		getNotes: function() {
			return notes;
		},

		createNote: function(noteMsg, lat, lng) {
			var url = urlTools.getURL('/create_note/', {
				content: noteMsg,
				lat: lat,
				lng: lng
			});

			$.ajax({
		  		url: url,
		  		success: successCreate,
		  		dataType: 'json'
			});
		}
	}

	function poll() {
		if (!polling) return;

		if (!lastPollTime) {
			console.log("1");
			lastPollTime = Math.round(new Date().getTime() / 1000);

			var url = urlTools.getURL('/get_notes', {
				ne_lat: 5.0,
				ne_long: 5.0,
				sw_lat: 5.0,
				sw_long: 5.0
			});

			$.ajax({
			  type:"get",
			  url: url,
			  success: successPoll,
			  dataType: 'json'
			});
		} else {
			console.log(lastPollTime);
			var url = urlTools.getURL('/get_recent_notes', {
				ne_lat: 5.0,
				ne_long: 5.0,
				sw_lat: 5.0,
				sw_long: 5.0,
				timestamp: lastPollTime
			});

			lastPollTime = Math.round(new Date().getTime() / 1000);

			$.ajax({
			  type:"get",
			  url: url,
			  success: successPoll,
			  dataType: 'json'
			});
		}

		$timeout(poll, 5000);
	}

	function successPoll(data) {
		console.log(data);
		notes = data;
	}

	function successCreate(data) {

	}

	return poller;
})

app.service("geoLocation", function() {
	var lat = "32.879996";
	var lng = "-117.237046";
	var locationReady = false;

	var locationServices = {
		computeLocation: function() {
			if (navigator.geolocation) {
		        navigator.geolocation.getCurrentPosition(setPosition);
		    } else {
		    	//set location to ucsd center
		    }
		},

		getLocation: function() {
			return [lat, lng];
		},

		locationReady: function() {
			return locationReady;
		}
	}

	function setPosition(position) {
		lat = position.coords.latitude;
		lng = position.coords.longitude;
		locationReady = true;
	}

	return locationServices;
});

app.directive("mapHandler", function() {
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, $rootScope, notePoller, geoLocation, $timeout) {
			$scope.currPos = ["32.879996", "-117.237046"];
			$scope.notes = [];

			geoLocation.computeLocation();

			$timeout(updateAll, 2000);

			$scope.map = L.map('map').setView($scope.currPos, 15);

			L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
				maxZoom: 18,
				id: 'examples.map-i875mjb7'
			}).addTo($scope.map);

			var currLocationMarker = L.marker($scope.currPos).addTo($scope.map);

			var markers = L.markerClusterGroup();

			notePoller.startPolling();

			function updateLocation() {
				currLocationMarker.setLatLng($scope.currPos);
			}

			function updateAll() {
				if (geoLocation.locationReady()) {
					$scope.currPos = geoLocation.getLocation();
					updateLocation();
				}
				$scope.notes = notePoller.getNotes();

				angular.forEach($scope.notes, function(note) {
					 var redMarker = L.AwesomeMarkers.icon({
					    icon: 'cutlery',
					    markerColor: 'red'
					  });

					  var marker = L.marker([note.lat, note.lng], {icon: redMarker}).addTo($scope.map);
					  marker.bindPopup(note.content);
					  markers.addLayer(marker);
				});
				$scope.map.addLayer(markers);
				$timeout(updateAll, 5100);
			}
		}
	}
});

app.directive("viewNotesInterface", function() {
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, notePoller, geoLocation) {
			$scope.notes = [];

			$scope.$watch(notePoller.getNotes(), function() {

			})
		}
	}
});

app.directive("createNoteInterface", function() {
	return {
		restrict: 'A',
		replace: true,
		controller: function($scope, $timeout, notePoller, geoLocation) {
			$scope.notes = []

			$timeout(updateAll, 2000);

			function updateAll() {

			}
		}
	}
});

app.controller("GeonoteController", [ "$scope", 'notePoller', 'geoLocation', function($scope, notePoller, geoLocation) {

}]);
<|endoftext|>


<commit_before>export const nicfiLayers = ["2015-12_2016-05",
                            "2016-06_2016-11",
                            "2016-12_2017-05",
                            "2017-06_2017-11",
                            "2017-12_2018-05",
                            "2018-06_2018-11",
                            "2018-12_2019-05",
                            "2019-06_2019-11",
                            "2019-12_2020-05",
                            "2020-06_2020-08",
                            "2020-09",
                            "2020-10",
                            "2020-11",
                            "2020-12"];

export const imageryOptions = [
    // Default type is text, default parent is none, a referenced parent must be entered as a json string
    // Parameters can be defined one level deep. {paramParent: {paramChild: "", fields: "", fromJsonStr: ""}}
    {
        type: "GeoServer",
        label: "WMS Imagery",
        params: [
            {key: "geoserverUrl", display: "WMS URL"},
            {key: "LAYERS", display: "WMS Layer Name", parent: "geoserverParams"},
            {
                key: "geoserverParams",
                display: "Additional WMS Params (as JSON object)", // TODO, add {} around params if missing
                required: false,
                type: "JSON",
            },
        ],
        // FIXME, add url if help document is created.
    },
    {
        type: "BingMaps",
        label: "Bing Maps",
        params: [
            {
                key: "imageryId",
                display: "Imagery Id",
                type: "select",
                options: [
                    {label: "Aerial", value: "Aerial"},
                    {label: "Aerial with Labels", value: "AerialWithLabels"},
                ],
            },
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/getting-a-bing-maps-key",
    },
    {
        type: "Planet",
        label: "Planet Monthly",
        params: [
            {key: "year", display: "Default Year", type: "number"},
            {key: "month", display: "Default Month", type: "number"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://developers.planet.com/docs/quickstart/getting-started/",
    },
    {
        type: "PlanetDaily",
        label: "Planet Daily",
        params: [
            {key: "accessToken", display: "Access Token"},
            {key: "startDate", display: "Start Date", type: "date"},
            {key: "endDate", display: "End Date", type: "date"},
        ],
        url: "https://developers.planet.com/docs/quickstart/getting-started/",
    },
    {
        type: "PlanetNICFI",
        label: "Planet NICFI",
        params: [
            {key: "accessToken", display: "Access Token"},
            {
                key: "time",
                display: "Default Time",
                type: "select",
                options: [
                    {label: "Newest Available", value: "newest"},
                    ...nicfiLayers.map(layer => ({label: layer, value: layer}))],
            },
            {
                key: "band",
                display: "Default Band",
                type: "select",
                options: [
                    {label: "Visible", value: "rgb"},
                    {label: "Infrared", value: "cir"},
                ],
            },
        ],
        url: "https://assets.planet.com/docs/NICFI_UserGuidesFAQ.pdf",
    },
    {
        type: "SecureWatch",
        params: [
            {
                key: "baseUrl",
                display: "Base URL",
                type: "select",
                options: [
                    {label: "https://securewatch.digitalglobe.com", value: "https://securewatch.digitalglobe.com"},
                    {label: "https://services.digitalglobe.com", value: "https://services.digitalglobe.com"},
                ],
            },
            {key: "connectid", display: "Connect ID"},
            {
                key: "startDate",
                display: "Start Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "endDate",
                display: "End Date",
                type: "date",
            },
        ],
    },
    {
        type: "Sentinel1",
        label: "Sentinel 1",
        params: [
            {
                key: "year",
                display: "Default Year",
                type: "number",
                options: {min: "2014", max: new Date().getFullYear().toString(), step: "1"},
            },
            {key: "month", display: "Default Month", type: "number", options: {min: "1", max: "12", step: "1"}},
            {
                key: "bandCombination",
                display: "Band Combination",
                type: "select",
                options: [
                    {label: "VH,VV,VH/VV", value: "VH,VV,VH/VV"},
                    {label: "VH,VV,VV/VH", value: "VH,VV,VV/VH"},
                    {label: "VV,VH,VV/VH", value: "VV,VH,VV/VH"},
                    {label: "VV,VH,VH/VV", value: "VV,VH,VH/VV"},
                ],
            },
            {key: "min", display: "Min", type: "number", options: {step: "0.01"}},
            {key: "max", display: "Max", type: "number", options: {step: "0.01"}},
        ],
    },
    {
        type: "Sentinel2",
        label: "Sentinel 2",
        params: [
            {
                key: "year",
                display: "Default Year",
                type: "number",
                options: {min: "2015", max: new Date().getFullYear().toString(), step: "1"},
            },
            {key: "month", display: "Default Month", type: "number", options: {min: "1", max: "12", step: "1"}},
            {
                key: "bandCombination",
                display: "Band Combination",
                type: "select",
                options: [
                    {label: "True Color", value: "TrueColor"},
                    {label: "False Color Infrared", value: "FalseColorInfrared"},
                    {label: "False Color Urban", value: "FalseColorUrban"},
                    {label: "Agriculture", value: "Agriculture"},
                    {label: "Healthy Vegetation", value: "HealthyVegetation"},
                    {label: "Short Wave Infrared", value: "ShortWaveInfrared"},
                ],
            },
            {key: "min", display: "Min", type: "number", options: {step: "0.01"}},
            {key: "max", display: "Max", type: "number", options: {step: "0.01"}},
            {key: "cloudScore", display: "Cloud Score", type: "number", options: {min: "0", max: "100", step: "1"}},
        ],
    },
    {
        type: "GEEImage",
        label: "GEE Image Asset",
        params: [
            {
                key: "imageId",
                display: "Asset ID",
                options: {placeholder: "USDA/NAIP/DOQQ/n_4207309_se_18_1_20090525"},
            },
            {
                key: "imageVisParams",
                display: "Visualization Parameters (JSON format)",
                type: "JSON",
                options: {placeholder: "{\"bands\": [\"R\", \"G\", \"B\"], \"min\": 90, \"max\": 210}"},
            },
        ],
    },
    {
        type: "GEEImageCollection",
        label: "GEE ImageCollection Asset",
        params: [
            {
                key: "collectionId",
                display: "Asset ID",
                options: {placeholder: "LANDSAT/LC08/C01/T1_SR"},
            },
            {
                key: "startDate",
                display: "Start Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "endDate",
                display: "End Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "collectionVisParams",
                display: "Visualization Parameters (JSON format)",
                type: "JSON",
                options: {placeholder: "{\"bands\": [\"B4\", \"B3\", \"B2\"], \"min\": 0, \"max\": 2000}"},
            },
        ],
    },
    {
        type: "MapBoxRaster",
        label: "Mapbox Raster",
        params: [
            {key: "layerName", display: "Layer Name"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.mapbox.com/help/glossary/raster-tiles-api/",
    },
    {
        type: "MapBoxStatic",
        label: "Mapbox Static",
        params: [
            {key: "userName", display: "User Name"},
            {key: "mapStyleId", display: "Map Style Id"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.mapbox.com/help/glossary/static-tiles-api/",
    },
];
<commit_msg>Fix formatting<commit_after>export const nicfiLayers = ["2015-12_2016-05",
                            "2016-06_2016-11",
                            "2016-12_2017-05",
                            "2017-06_2017-11",
                            "2017-12_2018-05",
                            "2018-06_2018-11",
                            "2018-12_2019-05",
                            "2019-06_2019-11",
                            "2019-12_2020-05",
                            "2020-06_2020-08",
                            "2020-09",
                            "2020-10",
                            "2020-11",
                            "2020-12"];

export const imageryOptions = [
    // Default type is text, default parent is none, a referenced parent must be entered as a json string
    // Parameters can be defined one level deep. {paramParent: {paramChild: "", fields: "", fromJsonStr: ""}}
    {
        type: "GeoServer",
        label: "WMS Imagery",
        params: [
            {key: "geoserverUrl", display: "WMS URL"},
            {key: "LAYERS", display: "WMS Layer Name", parent: "geoserverParams"},
            {
                key: "geoserverParams",
                display: "Additional WMS Params (as JSON object)", // TODO, add {} around params if missing
                required: false,
                type: "JSON",
            },
        ],
        // FIXME, add url if help document is created.
    },
    {
        type: "BingMaps",
        label: "Bing Maps",
        params: [
            {
                key: "imageryId",
                display: "Imagery Id",
                type: "select",
                options: [
                    {label: "Aerial", value: "Aerial"},
                    {label: "Aerial with Labels", value: "AerialWithLabels"},
                ],
            },
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.microsoft.com/en-us/bingmaps/getting-started/bing-maps-dev-center-help/getting-a-bing-maps-key",
    },
    {
        type: "Planet",
        label: "Planet Monthly",
        params: [
            {key: "year", display: "Default Year", type: "number"},
            {key: "month", display: "Default Month", type: "number"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://developers.planet.com/docs/quickstart/getting-started/",
    },
    {
        type: "PlanetDaily",
        label: "Planet Daily",
        params: [
            {key: "accessToken", display: "Access Token"},
            {key: "startDate", display: "Start Date", type: "date"},
            {key: "endDate", display: "End Date", type: "date"},
        ],
        url: "https://developers.planet.com/docs/quickstart/getting-started/",
    },
    {
        type: "PlanetNICFI",
        label: "Planet NICFI",
        params: [
            {key: "accessToken", display: "Access Token"},
            {
                key: "time",
                display: "Default Time",
                type: "select",
                options: [
                    {label: "Newest Available", value: "newest"},
                    ...nicfiLayers.map(layer => ({label: layer, value: layer})),
                ],
            },
            {
                key: "band",
                display: "Default Band",
                type: "select",
                options: [
                    {label: "Visible", value: "rgb"},
                    {label: "Infrared", value: "cir"},
                ],
            },
        ],
        url: "https://assets.planet.com/docs/NICFI_UserGuidesFAQ.pdf",
    },
    {
        type: "SecureWatch",
        params: [
            {
                key: "baseUrl",
                display: "Base URL",
                type: "select",
                options: [
                    {label: "https://securewatch.digitalglobe.com", value: "https://securewatch.digitalglobe.com"},
                    {label: "https://services.digitalglobe.com", value: "https://services.digitalglobe.com"},
                ],
            },
            {key: "connectid", display: "Connect ID"},
            {
                key: "startDate",
                display: "Start Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "endDate",
                display: "End Date",
                type: "date",
            },
        ],
    },
    {
        type: "Sentinel1",
        label: "Sentinel 1",
        params: [
            {
                key: "year",
                display: "Default Year",
                type: "number",
                options: {min: "2014", max: new Date().getFullYear().toString(), step: "1"},
            },
            {key: "month", display: "Default Month", type: "number", options: {min: "1", max: "12", step: "1"}},
            {
                key: "bandCombination",
                display: "Band Combination",
                type: "select",
                options: [
                    {label: "VH,VV,VH/VV", value: "VH,VV,VH/VV"},
                    {label: "VH,VV,VV/VH", value: "VH,VV,VV/VH"},
                    {label: "VV,VH,VV/VH", value: "VV,VH,VV/VH"},
                    {label: "VV,VH,VH/VV", value: "VV,VH,VH/VV"},
                ],
            },
            {key: "min", display: "Min", type: "number", options: {step: "0.01"}},
            {key: "max", display: "Max", type: "number", options: {step: "0.01"}},
        ],
    },
    {
        type: "Sentinel2",
        label: "Sentinel 2",
        params: [
            {
                key: "year",
                display: "Default Year",
                type: "number",
                options: {min: "2015", max: new Date().getFullYear().toString(), step: "1"},
            },
            {key: "month", display: "Default Month", type: "number", options: {min: "1", max: "12", step: "1"}},
            {
                key: "bandCombination",
                display: "Band Combination",
                type: "select",
                options: [
                    {label: "True Color", value: "TrueColor"},
                    {label: "False Color Infrared", value: "FalseColorInfrared"},
                    {label: "False Color Urban", value: "FalseColorUrban"},
                    {label: "Agriculture", value: "Agriculture"},
                    {label: "Healthy Vegetation", value: "HealthyVegetation"},
                    {label: "Short Wave Infrared", value: "ShortWaveInfrared"},
                ],
            },
            {key: "min", display: "Min", type: "number", options: {step: "0.01"}},
            {key: "max", display: "Max", type: "number", options: {step: "0.01"}},
            {key: "cloudScore", display: "Cloud Score", type: "number", options: {min: "0", max: "100", step: "1"}},
        ],
    },
    {
        type: "GEEImage",
        label: "GEE Image Asset",
        params: [
            {
                key: "imageId",
                display: "Asset ID",
                options: {placeholder: "USDA/NAIP/DOQQ/n_4207309_se_18_1_20090525"},
            },
            {
                key: "imageVisParams",
                display: "Visualization Parameters (JSON format)",
                type: "JSON",
                options: {placeholder: "{\"bands\": [\"R\", \"G\", \"B\"], \"min\": 90, \"max\": 210}"},
            },
        ],
    },
    {
        type: "GEEImageCollection",
        label: "GEE ImageCollection Asset",
        params: [
            {
                key: "collectionId",
                display: "Asset ID",
                options: {placeholder: "LANDSAT/LC08/C01/T1_SR"},
            },
            {
                key: "startDate",
                display: "Start Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "endDate",
                display: "End Date",
                type: "date",
                options: {max: new Date().toJSON().split("T")[0]},
            },
            {
                key: "collectionVisParams",
                display: "Visualization Parameters (JSON format)",
                type: "JSON",
                options: {placeholder: "{\"bands\": [\"B4\", \"B3\", \"B2\"], \"min\": 0, \"max\": 2000}"},
            },
        ],
    },
    {
        type: "MapBoxRaster",
        label: "Mapbox Raster",
        params: [
            {key: "layerName", display: "Layer Name"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.mapbox.com/help/glossary/raster-tiles-api/",
    },
    {
        type: "MapBoxStatic",
        label: "Mapbox Static",
        params: [
            {key: "userName", display: "User Name"},
            {key: "mapStyleId", display: "Map Style Id"},
            {key: "accessToken", display: "Access Token"},
        ],
        url: "https://docs.mapbox.com/help/glossary/static-tiles-api/",
    },
];
<|endoftext|>


<commit_before>/**
 * Xibo - Digital Signage - http://www.xibo.org.uk
 * Copyright (C) 2009-2020 Xibo Signage Ltd
 *
 * This file is part of Xibo.
 *
 * Xibo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Xibo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
 */
jQuery.fn.extend({
    xiboTextRender: function(options, items) {

        // Default options
        var defaults = {
            "fx": "none",
            "duration": "50",
            "durationIsPerItem": false,
            "numItems": 0,
            "takeItemsFrom": "start",
            "reverseOrder": 0,
            "itemsPerPage": 0,
            "speed": "2",
            "previewWidth": 0,
            "previewHeight": 0,
            "scaleOverride": 0,
            "randomiseItems": 0,
            "marqueeInlineSelector": ".item, .item p"
        };

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

        // Calculate the dimensions of this item based on the preview/original dimensions
        var width = height = 0;
        if (options.previewWidth === 0 || options.previewHeight === 0) {
            width = options.originalWidth;
            height = options.originalHeight;
        }
        else {
            width = options.previewWidth;
            height = options.previewHeight;
        }

        if (options.scaleOverride !== 0) {
            width = width / options.scaleOverride;
            height = height / options.scaleOverride;
        }

        var paddingBottom = paddingRight = 0;
        if (options.widgetDesignWidth > 0 && options.widgetDesignHeight > 0) {
            if(options.itemsPerPage > 0){
                if($(window).width() > $(window).height()){
                    //Landscape or square size plus padding
                    options.widgetDesignWidth = (options.itemsPerPage * options.widgetDesignWidth) + (options.widgetDesignPadding * (options.itemsPerPage - 1));
                    options.widgetDesignHeight = options.widgetDesignHeight;
                    width = options.widgetDesignWidth;
                    height = options.widgetDesignHeight;
                    paddingRight = options.widgetDesignPadding;
                } else {
                    //Portrait size plus padding
                    options.widgetDesignHeight = (options.itemsPerPage * options.widgetDesignHeight) + (options.widgetDesignPadding * (options.itemsPerPage - 1));
                    options.widgetDesignWidth = options.widgetDesignWidth;
                    width = options.widgetDesignWidth;
                    height = options.widgetDesignHeight;
                    paddingBottom = options.widgetDesignPadding;
                }
            }
        }

        // For each matched element
        this.each(function() {

            //console.log("[Xibo] Selected: " + this.tagName.toLowerCase());
            //console.log("[Xibo] Options: " + JSON.stringify(options));

            // 1st Objective - filter the items array we have been given
            // settings involved:
            //  items,
            //  numItems (ticker number of items from the start/end),
            //  takeItemsFrom (ticker sort or reverse sort the array)
            //  randomiseItems (randomise the items)
            if (options.randomiseItems === 1) {
                // Sort the items in a random order (considering the entire list)
                // Durstenfeld shuffle
                // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
                // https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
                for (var i = items.length - 1; i > 0; i--) {
                    var j = Math.floor(Math.random() * (i + 1));
                    var temp = items[i];
                    items[i] = items[j];
                    items[j] = temp;
                }
            }

            if (options.takeItemsFrom === "end") {
                //console.log("[Xibo] Reversing items");
                items.reverse();
            }

            // Make sure the num items is not greater than the actual number of items
            //console.log("[Xibo] Module requested " + options.numItems + " there are " + items.length + " in the array of items");

            if (options.numItems > items.length || options.numItems === 0)
                options.numItems = items.length;

            // Get a new array with only the first N elements
            items = items.slice(0, options.numItems);

            // Reverse the items again (so they are in the correct order)
            if ( (options.takeItemsFrom === "end" && options.reverseOrder === 0) || (options.takeItemsFrom === "start" && options.reverseOrder === 1)) {
                //console.log("[Xibo] Reversing items");
                items.reverse();
            }

            // 2nd objective - put the items on the page
            // settings involved:
            //  fx (if we are single we might need to configure some pages for this)
            //  itemsPerPage (tells us how many items to put on per page)
            //console.log("[Xibo] Putting " + options.numItems + " Items on the page");

            // Store the number of items (we might change this to number of pages)
            var numberOfItems = options.numItems;

            // How many pages to we need?
            var numberOfPages = (options.itemsPerPage > 0) ? Math.ceil(options.numItems / options.itemsPerPage) : options.numItems;
            var itemsThisPage = 1;

            //console.log("[Xibo] We need to have " + numberOfPages + " pages");
            var appendTo = this;

            // Loop around each of the items we have been given and append them to this element (in a div)
            for (var i = 0; i < items.length; i++) {

                // We don't add any pages for marquee / none transitions.
                if (options.fx != "none" &&
                    options.fx != "marqueeLeft" &&
                    options.fx != "marqueeRight" &&
                    options.fx != "marqueeUp" &&
                    options.fx != "marqueeDown") {

                    // If we need to set pages, have we switched over to a new page?
                    if (options.itemsPerPage > 1 && (itemsThisPage >= options.itemsPerPage || i === 0)) {
                        // Append a new page to the body
                        appendTo = $("<div/>").addClass("page").appendTo(this);

                        // Reset the row count on this page
                        itemsThisPage = 0;
                    }
                }

                // For each item output a DIV
                $("<div/>")
                    .addClass("item")
                    .html(items[i]).appendTo(appendTo);

                itemsThisPage++;
            }

            // 4th objective - move the items around, start the timer
            // settings involved:
            //  fx (the way we are moving effects the HTML required)
            //  speed (how fast we need to move)
            var marquee = false;

            if (options.fx == "none") {
                // Do nothing
            }
            else if (options.fx != "marqueeLeft" && options.fx != "marqueeRight" && options.fx != "marqueeUp" && options.fx != "marqueeDown") {

                // Make sure the speed is something sensible
                options.speed = (options.speed <= 200) ? 1000 : options.speed;

                // Cycle slides are either page or item
                var slides = (options.itemsPerPage > 1) ? ".page" : ".item";

                // If we only have 1 item, then we are in trouble and need to duplicate it.
                if ($(slides).length <= 1 && options.type == 'text') {
                    // Change our slide tag to be the paragraphs inside
                    slides = slides + ' p';

                    // Change the number of items
                    numberOfItems = $(slides).length;
                }

                var numberOfSlides = (options.itemsPerPage > 1) ? numberOfPages : numberOfItems;
                var duration = (options.durationIsPerItem) ? options.duration : options.duration / numberOfSlides;

                //console.log("[Xibo] initialising the cycle2 plugin with " + numberOfSlides + " slides and selector " + slides + ". Duration per slide is " + duration + " seconds.");

                // Set the content div to the height of the original window
                $(this).css("height", height);

                // Set the width on the cycled slides
                $(slides, this).css({
                    width: width,
                    height: height
                });

                var timeout = duration * 1000;

                if (options.fx !== "noTransition") {
                    timeout = timeout - (options.speed * 0.7);
                }

                // Cycle handles this for us
                $(this).cycle({
                    fx: (options.fx === "noTransition") ? "none" : options.fx,
                    speed: (options.fx === "noTransition") ? 1 : options.speed,
                    timeout: timeout,
                    slides: "> " + slides
                });
            }
            else if (options.fx == "marqueeLeft" || options.fx == "marqueeRight") {
                marquee = true;
                options.direction = ((options.fx == "marqueeLeft") ? "left" : "right");

                // Make sure the speed is something sensible
                options.speed = (options.speed == 0) ? 1 : options.speed;

                // Stack the articles up and move them across the screen
                $(options.marqueeInlineSelector, this).css({
                    display: "inline",
                    "padding-left": "10px"
                });
            }
            else if (options.fx == "marqueeUp" || options.fx == "marqueeDown") {
                // We want a marquee
                marquee = true;
                options.direction = ((options.fx == "marqueeUp") ? "up" : "down");

                // Make sure the speed is something sensible
                options.speed = (options.speed == 0) ? 1 : options.speed;
            }

            if (marquee) {
                // Which marquee to use?
                var nua = navigator.userAgent;
                /* The intention was to allow Chrome based android to benefit from the new marquee
                   unfortunately though, it doesn't appear to work.
                   Maybe this is due to Chrome verison? Android tends to have quite an old version.
                var is_android = ((nua.indexOf('Mozilla/5.0') > -1
                    && nua.indexOf('Android') > -1
                    && nua.indexOf('AppleWebKit') > -1)
                    && !(nua.indexOf('Chrome') > -1));*/
                var is_android = nua.indexOf('Android') > -1;

                // Create a DIV to scroll, and put this inside the body
                var scroller = $("<div/>")
                    .addClass("scroll");

                if (!is_android) {
                    // in old marquee scroll delay is 85 milliseconds
                    // options.speed is the scrollamount which is the number of pixels per 85 milliseconds
                    // our new plugin speed is pixels per second
                    scroller.attr({
                        "data-is-legacy": false,
                        "data-speed": options.speed / 25 * 1000,
                        "data-direction": options.direction,
                        scaleFactor: options.scaleFactor
                    });
                } else {
                    scroller.attr({
                        "data-is-legacy": true,
                        scrollamount: options.speed,
                        scaleFactor: options.scaleFactor,
                        behaviour: "scroll",
                        direction: options.direction,
                        height: height,
                        width: width
                    });
                }

                $(this).wrapInner(scroller);

                // Set some options on the extra DIV and make it a marquee
                if (!is_android) {
                    $(this).find('.scroll').marquee();
                } else {
                    $(this).find('.scroll').overflowMarquee();
                }

                // Correct for up / down
                if (options.fx === "marqueeUp" || options.fx === "marqueeDown") {
                    $(this).children().children().css({"white-space": "normal", float: "none"});
                }
            }

            // Add aditional padding to the items
            if (paddingRight > 0 || paddingBottom > 0) {
                // Add padding to all item elements
                $(".item").css("padding", "0px " + paddingRight + "px " + paddingBottom  + "px 0px");

                // Exclude the last item on the page and the last on the content ( if there is no pages )
                $(".page .item:last-child").css("padding", 0);
                $("#content .item:last-child").css("padding", 0);
            }
        });

        return $(this);
    }
});
<commit_msg>Text Widget : Marquee up/down effects do not work fixes xibosignage/xibo#2639<commit_after>/**
 * Xibo - Digital Signage - http://www.xibo.org.uk
 * Copyright (C) 2009-2020 Xibo Signage Ltd
 *
 * This file is part of Xibo.
 *
 * Xibo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Xibo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
 */
jQuery.fn.extend({
    xiboTextRender: function(options, items) {

        // Default options
        var defaults = {
            "fx": "none",
            "duration": "50",
            "durationIsPerItem": false,
            "numItems": 0,
            "takeItemsFrom": "start",
            "reverseOrder": 0,
            "itemsPerPage": 0,
            "speed": "2",
            "previewWidth": 0,
            "previewHeight": 0,
            "scaleOverride": 0,
            "randomiseItems": 0,
            "marqueeInlineSelector": ".item, .item p"
        };

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

        // Calculate the dimensions of this item based on the preview/original dimensions
        var width = height = 0;
        if (options.previewWidth === 0 || options.previewHeight === 0) {
            width = options.originalWidth;
            height = options.originalHeight;
        }
        else {
            width = options.previewWidth;
            height = options.previewHeight;
        }

        if (options.scaleOverride !== 0) {
            width = width / options.scaleOverride;
            height = height / options.scaleOverride;
        }

        var paddingBottom = paddingRight = 0;
        if (options.widgetDesignWidth > 0 && options.widgetDesignHeight > 0) {
            if(options.itemsPerPage > 0){
                if($(window).width() > $(window).height()){
                    //Landscape or square size plus padding
                    options.widgetDesignWidth = (options.itemsPerPage * options.widgetDesignWidth) + (options.widgetDesignPadding * (options.itemsPerPage - 1));
                    options.widgetDesignHeight = options.widgetDesignHeight;
                    width = options.widgetDesignWidth;
                    height = options.widgetDesignHeight;
                    paddingRight = options.widgetDesignPadding;
                } else {
                    //Portrait size plus padding
                    options.widgetDesignHeight = (options.itemsPerPage * options.widgetDesignHeight) + (options.widgetDesignPadding * (options.itemsPerPage - 1));
                    options.widgetDesignWidth = options.widgetDesignWidth;
                    width = options.widgetDesignWidth;
                    height = options.widgetDesignHeight;
                    paddingBottom = options.widgetDesignPadding;
                }
            }
        }

        // For each matched element
        this.each(function() {

            //console.log("[Xibo] Selected: " + this.tagName.toLowerCase());
            //console.log("[Xibo] Options: " + JSON.stringify(options));

            // 1st Objective - filter the items array we have been given
            // settings involved:
            //  items,
            //  numItems (ticker number of items from the start/end),
            //  takeItemsFrom (ticker sort or reverse sort the array)
            //  randomiseItems (randomise the items)
            if (options.randomiseItems === 1) {
                // Sort the items in a random order (considering the entire list)
                // Durstenfeld shuffle
                // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
                // https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
                for (var i = items.length - 1; i > 0; i--) {
                    var j = Math.floor(Math.random() * (i + 1));
                    var temp = items[i];
                    items[i] = items[j];
                    items[j] = temp;
                }
            }

            if (options.takeItemsFrom === "end") {
                //console.log("[Xibo] Reversing items");
                items.reverse();
            }

            // Make sure the num items is not greater than the actual number of items
            //console.log("[Xibo] Module requested " + options.numItems + " there are " + items.length + " in the array of items");

            if (options.numItems > items.length || options.numItems === 0)
                options.numItems = items.length;

            // Get a new array with only the first N elements
            items = items.slice(0, options.numItems);

            // Reverse the items again (so they are in the correct order)
            if ( (options.takeItemsFrom === "end" && options.reverseOrder === 0) || (options.takeItemsFrom === "start" && options.reverseOrder === 1)) {
                //console.log("[Xibo] Reversing items");
                items.reverse();
            }

            // 2nd objective - put the items on the page
            // settings involved:
            //  fx (if we are single we might need to configure some pages for this)
            //  itemsPerPage (tells us how many items to put on per page)
            //console.log("[Xibo] Putting " + options.numItems + " Items on the page");

            // Store the number of items (we might change this to number of pages)
            var numberOfItems = options.numItems;

            // How many pages to we need?
            var numberOfPages = (options.itemsPerPage > 0) ? Math.ceil(options.numItems / options.itemsPerPage) : options.numItems;
            var itemsThisPage = 1;

            //console.log("[Xibo] We need to have " + numberOfPages + " pages");
            var appendTo = this;

            // Loop around each of the items we have been given and append them to this element (in a div)
            for (var i = 0; i < items.length; i++) {

                // We don't add any pages for marquee / none transitions.
                if (options.fx != "none" &&
                    options.fx != "marqueeLeft" &&
                    options.fx != "marqueeRight" &&
                    options.fx != "marqueeUp" &&
                    options.fx != "marqueeDown") {

                    // If we need to set pages, have we switched over to a new page?
                    if (options.itemsPerPage > 1 && (itemsThisPage >= options.itemsPerPage || i === 0)) {
                        // Append a new page to the body
                        appendTo = $("<div/>").addClass("page").appendTo(this);

                        // Reset the row count on this page
                        itemsThisPage = 0;
                    }
                }

                // For each item output a DIV
                $("<div/>")
                    .addClass("item")
                    .html(items[i]).appendTo(appendTo);

                itemsThisPage++;
            }

            // 4th objective - move the items around, start the timer
            // settings involved:
            //  fx (the way we are moving effects the HTML required)
            //  speed (how fast we need to move)
            var marquee = false;

            if (options.fx == "none") {
                // Do nothing
            }
            else if (options.fx != "marqueeLeft" && options.fx != "marqueeRight" && options.fx != "marqueeUp" && options.fx != "marqueeDown") {

                // Make sure the speed is something sensible
                options.speed = (options.speed <= 200) ? 1000 : options.speed;

                // Cycle slides are either page or item
                var slides = (options.itemsPerPage > 1) ? ".page" : ".item";

                // If we only have 1 item, then we are in trouble and need to duplicate it.
                if ($(slides).length <= 1 && options.type == 'text') {
                    // Change our slide tag to be the paragraphs inside
                    slides = slides + ' p';

                    // Change the number of items
                    numberOfItems = $(slides).length;
                }

                var numberOfSlides = (options.itemsPerPage > 1) ? numberOfPages : numberOfItems;
                var duration = (options.durationIsPerItem) ? options.duration : options.duration / numberOfSlides;

                //console.log("[Xibo] initialising the cycle2 plugin with " + numberOfSlides + " slides and selector " + slides + ". Duration per slide is " + duration + " seconds.");

                // Set the content div to the height of the original window
                $(this).css("height", height);

                // Set the width on the cycled slides
                $(slides, this).css({
                    width: width,
                    height: height
                });

                var timeout = duration * 1000;

                if (options.fx !== "noTransition") {
                    timeout = timeout - (options.speed * 0.7);
                }

                // Cycle handles this for us
                $(this).cycle({
                    fx: (options.fx === "noTransition") ? "none" : options.fx,
                    speed: (options.fx === "noTransition") ? 1 : options.speed,
                    timeout: timeout,
                    slides: "> " + slides
                });
            }
            else if (options.fx == "marqueeLeft" || options.fx == "marqueeRight") {
                marquee = true;
                options.direction = ((options.fx == "marqueeLeft") ? "left" : "right");

                // Make sure the speed is something sensible
                options.speed = (options.speed == 0) ? 1 : options.speed;

                // Stack the articles up and move them across the screen
                $(options.marqueeInlineSelector, this).css({
                    display: "inline",
                    "padding-left": "10px"
                });
            }
            else if (options.fx == "marqueeUp" || options.fx == "marqueeDown") {
                // We want a marquee
                marquee = true;
                options.direction = ((options.fx == "marqueeUp") ? "up" : "down");

                // Make sure the speed is something sensible
                options.speed = (options.speed == 0) ? 1 : options.speed;
            }

            if (marquee) {
                // Which marquee to use?
                var nua = navigator.userAgent;
                /* The intention was to allow Chrome based android to benefit from the new marquee
                   unfortunately though, it doesn't appear to work.
                   Maybe this is due to Chrome verison? Android tends to have quite an old version.
                var is_android = ((nua.indexOf('Mozilla/5.0') > -1
                    && nua.indexOf('Android') > -1
                    && nua.indexOf('AppleWebKit') > -1)
                    && !(nua.indexOf('Chrome') > -1));*/
                var is_android = nua.indexOf('Android') > -1;

                // Create a DIV to scroll, and put this inside the body
                var scroller = $("<div/>")
                    .addClass("scroll");

                if (!is_android) {
                    // in old marquee scroll delay is 85 milliseconds
                    // options.speed is the scrollamount which is the number of pixels per 85 milliseconds
                    // our new plugin speed is pixels per second
                    scroller.attr({
                        "data-is-legacy": false,
                        "data-speed": options.speed / 25 * 1000,
                        "data-direction": options.direction,
                        scaleFactor: options.scaleFactor
                    });
                } else {
                    scroller.attr({
                        "data-is-legacy": true,
                        scrollamount: options.speed,
                        scaleFactor: options.scaleFactor,
                        behaviour: "scroll",
                        direction: options.direction,
                        height: height,
                        width: width
                    });
                }

                $(this).wrapInner(scroller);

                // Correct for up / down
                if (options.fx === "marqueeUp" || options.fx === "marqueeDown") {
                    $(this).css('height', '100%');
                    $(this).find('.scroll').css('height', '100%').children().css({"white-space": "normal", float: "none"});
                }

                // Set some options on the extra DIV and make it a marquee
                if (!is_android) {
                    $(this).find('.scroll').marquee();
                } else {
                    $(this).find('.scroll').overflowMarquee();
                }
            }

            // Add aditional padding to the items
            if (paddingRight > 0 || paddingBottom > 0) {
                // Add padding to all item elements
                $(".item").css("padding", "0px " + paddingRight + "px " + paddingBottom  + "px 0px");

                // Exclude the last item on the page and the last on the content ( if there is no pages )
                $(".page .item:last-child").css("padding", 0);
                $("#content .item:last-child").css("padding", 0);
            }
        });

        return $(this);
    }
});
<|endoftext|>