Skip to content
This repository has been archived by the owner on Jul 29, 2019. It is now read-only.

Adding unit tests for lib/shared #3600

Merged
merged 22 commits into from
Nov 4, 2017
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/shared/Activator.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ Activator.prototype.destroy = function () {
if (this.onClick) {
document.body.removeEventListener('click', this.onClick);
}

// remove keycharm
if (this.keycharm !== undefined) {
this.keycharm.destroy();
}
this.keycharm = null;
// cleanup hammer instances
this.hammer.destroy();
this.hammer = null;
Expand Down
5 changes: 3 additions & 2 deletions lib/shared/ColorPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ let Hammer = require('../module/hammer');
let hammerUtil = require('../hammerUtil');
let util = require('../util');


var htmlColors = {black: '#000000', navy: '#000080', darkblue: '#00008B', mediumblue: '#0000CD', blue: '#0000FF', darkgreen: '#006400', green: '#008000', teal: '#008080', darkcyan: '#008B8B', deepskyblue: '#00BFFF', darkturquoise: '#00CED1', mediumspringgreen: '#00FA9A', lime: '#00FF00', springgreen: '#00FF7F', aqua: '#00FFFF', cyan: '#00FFFF', midnightblue: '#191970', dodgerblue: '#1E90FF', lightseagreen: '#20B2AA', forestgreen: '#228B22', seagreen: '#2E8B57', darkslategray: '#2F4F4F', limegreen: '#32CD32', mediumseagreen: '#3CB371', turquoise: '#40E0D0', royalblue: '#4169E1', steelblue: '#4682B4', darkslateblue: '#483D8B', mediumturquoise: '#48D1CC', indigo: '#4B0082', darkolivegreen: '#556B2F', cadetblue: '#5F9EA0', cornflowerblue: '#6495ED', mediumaquamarine: '#66CDAA', dimgray: '#696969', slateblue: '#6A5ACD', olivedrab: '#6B8E23', slategray: '#708090', lightslategray: '#778899', mediumslateblue: '#7B68EE', lawngreen: '#7CFC00', chartreuse: '#7FFF00', aquamarine: '#7FFFD4', maroon: '#800000', purple: '#800080', olive: '#808000', gray: '#808080', skyblue: '#87CEEB', lightskyblue: '#87CEFA', blueviolet: '#8A2BE2', darkred: '#8B0000', darkmagenta: '#8B008B', saddlebrown: '#8B4513', darkseagreen: '#8FBC8F', lightgreen: '#90EE90', mediumpurple: '#9370D8', darkviolet: '#9400D3', palegreen: '#98FB98', darkorchid: '#9932CC', yellowgreen: '#9ACD32', sienna: '#A0522D', brown: '#A52A2A', darkgray: '#A9A9A9', lightblue: '#ADD8E6', greenyellow: '#ADFF2F', paleturquoise: '#AFEEEE', lightsteelblue: '#B0C4DE', powderblue: '#B0E0E6', firebrick: '#B22222', darkgoldenrod: '#B8860B', mediumorchid: '#BA55D3', rosybrown: '#BC8F8F', darkkhaki: '#BDB76B', silver: '#C0C0C0', mediumvioletred: '#C71585', indianred: '#CD5C5C', peru: '#CD853F', chocolate: '#D2691E', tan: '#D2B48C', lightgrey: '#D3D3D3', palevioletred: '#D87093', thistle: '#D8BFD8', orchid: '#DA70D6', goldenrod: '#DAA520', crimson: '#DC143C', gainsboro: '#DCDCDC', plum: '#DDA0DD', burlywood: '#DEB887', lightcyan: '#E0FFFF', lavender: '#E6E6FA', darksalmon: '#E9967A', violet: '#EE82EE', palegoldenrod: '#EEE8AA', lightcoral: '#F08080', khaki: '#F0E68C', aliceblue: '#F0F8FF', honeydew: '#F0FFF0', azure: '#F0FFFF', sandybrown: '#F4A460', wheat: '#F5DEB3', beige: '#F5F5DC', whitesmoke: '#F5F5F5', mintcream: '#F5FFFA', ghostwhite: '#F8F8FF', salmon: '#FA8072', antiquewhite: '#FAEBD7', linen: '#FAF0E6', lightgoldenrodyellow: '#FAFAD2', oldlace: '#FDF5E6', red: '#FF0000', fuchsia: '#FF00FF', magenta: '#FF00FF', deeppink: '#FF1493', orangered: '#FF4500', tomato: '#FF6347', hotpink: '#FF69B4', coral: '#FF7F50', darkorange: '#FF8C00', lightsalmon: '#FFA07A', orange: '#FFA500', lightpink: '#FFB6C1', pink: '#FFC0CB', gold: '#FFD700', peachpuff: '#FFDAB9', navajowhite: '#FFDEAD', moccasin: '#FFE4B5', bisque: '#FFE4C4', mistyrose: '#FFE4E1', blanchedalmond: '#FFEBCD', papayawhip: '#FFEFD5', lavenderblush: '#FFF0F5', seashell: '#FFF5EE', cornsilk: '#FFF8DC', lemonchiffon: '#FFFACD', floralwhite: '#FFFAF0', snow: '#FFFAFA', yellow: '#FFFF00', lightyellow: '#FFFFE0', ivory: '#FFFFF0', white: '#FFFFFF'};

/**
* @param {number} [pixelRatio=1]
*/
Expand Down Expand Up @@ -78,7 +81,6 @@ class ColorPicker {
* @private
*/
_isColorString(color) {
var htmlColors = {black: '#000000',navy: '#000080',darkblue: '#00008B',mediumblue: '#0000CD',blue: '#0000FF',darkgreen: '#006400',green: '#008000',teal: '#008080',darkcyan: '#008B8B',deepskyblue: '#00BFFF',darkturquoise: '#00CED1',mediumspringgreen: '#00FA9A',lime: '#00FF00',springgreen: '#00FF7F',aqua: '#00FFFF',cyan: '#00FFFF',midnightblue: '#191970',dodgerblue: '#1E90FF',lightseagreen: '#20B2AA',forestgreen: '#228B22',seagreen: '#2E8B57',darkslategray: '#2F4F4F',limegreen: '#32CD32',mediumseagreen: '#3CB371',turquoise: '#40E0D0',royalblue: '#4169E1',steelblue: '#4682B4',darkslateblue: '#483D8B',mediumturquoise: '#48D1CC',indigo: '#4B0082',darkolivegreen: '#556B2F',cadetblue: '#5F9EA0',cornflowerblue: '#6495ED',mediumaquamarine: '#66CDAA',dimgray: '#696969',slateblue: '#6A5ACD',olivedrab: '#6B8E23',slategray: '#708090',lightslategray: '#778899',mediumslateblue: '#7B68EE',lawngreen: '#7CFC00',chartreuse: '#7FFF00',aquamarine: '#7FFFD4',maroon: '#800000',purple: '#800080',olive: '#808000',gray: '#808080',skyblue: '#87CEEB',lightskyblue: '#87CEFA',blueviolet: '#8A2BE2',darkred: '#8B0000',darkmagenta: '#8B008B',saddlebrown: '#8B4513',darkseagreen: '#8FBC8F',lightgreen: '#90EE90',mediumpurple: '#9370D8',darkviolet: '#9400D3',palegreen: '#98FB98',darkorchid: '#9932CC',yellowgreen: '#9ACD32',sienna: '#A0522D',brown: '#A52A2A',darkgray: '#A9A9A9',lightblue: '#ADD8E6',greenyellow: '#ADFF2F',paleturquoise: '#AFEEEE',lightsteelblue: '#B0C4DE',powderblue: '#B0E0E6',firebrick: '#B22222',darkgoldenrod: '#B8860B',mediumorchid: '#BA55D3',rosybrown: '#BC8F8F',darkkhaki: '#BDB76B',silver: '#C0C0C0',mediumvioletred: '#C71585',indianred: '#CD5C5C',peru: '#CD853F',chocolate: '#D2691E',tan: '#D2B48C',lightgrey: '#D3D3D3',palevioletred: '#D87093',thistle: '#D8BFD8',orchid: '#DA70D6',goldenrod: '#DAA520',crimson: '#DC143C',gainsboro: '#DCDCDC',plum: '#DDA0DD',burlywood: '#DEB887',lightcyan: '#E0FFFF',lavender: '#E6E6FA',darksalmon: '#E9967A',violet: '#EE82EE',palegoldenrod: '#EEE8AA',lightcoral: '#F08080',khaki: '#F0E68C',aliceblue: '#F0F8FF',honeydew: '#F0FFF0',azure: '#F0FFFF',sandybrown: '#F4A460',wheat: '#F5DEB3',beige: '#F5F5DC',whitesmoke: '#F5F5F5',mintcream: '#F5FFFA',ghostwhite: '#F8F8FF',salmon: '#FA8072',antiquewhite: '#FAEBD7',linen: '#FAF0E6',lightgoldenrodyellow: '#FAFAD2',oldlace: '#FDF5E6',red: '#FF0000',fuchsia: '#FF00FF',magenta: '#FF00FF',deeppink: '#FF1493',orangered: '#FF4500',tomato: '#FF6347',hotpink: '#FF69B4',coral: '#FF7F50',darkorange: '#FF8C00',lightsalmon: '#FFA07A',orange: '#FFA500',lightpink: '#FFB6C1',pink: '#FFC0CB',gold: '#FFD700',peachpuff: '#FFDAB9',navajowhite: '#FFDEAD',moccasin: '#FFE4B5',bisque: '#FFE4C4',mistyrose: '#FFE4E1',blanchedalmond: '#FFEBCD',papayawhip: '#FFEFD5',lavenderblush: '#FFF0F5',seashell: '#FFF5EE',cornsilk: '#FFF8DC',lemonchiffon: '#FFFACD',floralwhite: '#FFFAF0',snow: '#FFFAFA',yellow: '#FFFF00',lightyellow: '#FFFFE0',ivory: '#FFFFF0',white: '#FFFFFF'};
if (typeof color === 'string') {
return htmlColors[color];
}
Expand Down Expand Up @@ -359,7 +361,6 @@ class ColorPicker {
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);

this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
}

Expand Down
41 changes: 25 additions & 16 deletions lib/shared/Configurator.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class Configurator {
this.options.filter = options.join();
}
else if (typeof options === 'object') {
if (options == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really prefer it if the brace positioning is the same as the surrounding code

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch BTW. It's bitten me before that typeof null === 'object'. This should actually be checked in the entire code.

{
throw new TypeError('options cannot be null');
}
if (options.container !== undefined) {
this.options.container = options.container;
}
Expand Down Expand Up @@ -150,22 +154,7 @@ class Configurator {
counter++;
}
}

if (this.options.showButton === true) {
let generateButton = document.createElement('div');
generateButton.className = 'vis-configuration vis-config-button';
generateButton.innerHTML = 'generate options';
generateButton.onclick = () => {this._printOptions();};
generateButton.onmouseover = () => {generateButton.className = 'vis-configuration vis-config-button hover';};
generateButton.onmouseout = () => {generateButton.className = 'vis-configuration vis-config-button';};

this.optionsContainer = document.createElement('div');
this.optionsContainer.className = 'vis-configuration vis-config-option-container';

this.domElements.push(this.optionsContainer);
this.domElements.push(generateButton);
}

this._makeButton();
this._push();
//~ this.colorPicker.insertTo(this.container);
}
Expand Down Expand Up @@ -383,7 +372,27 @@ class Configurator {
this.popupHistory[itemIndex] = popupValue;
this._setupPopup(popupString, itemIndex);
}
}

/**
* make a button object
* @private
*/
_makeButton() {
if (this.options.showButton === true) {
let generateButton = document.createElement('div');
generateButton.className = 'vis-configuration vis-config-button';
generateButton.innerHTML = 'generate options';
generateButton.onclick = () => {this._printOptions();};
generateButton.onmouseover = () => {generateButton.className = 'vis-configuration vis-config-button hover';};
generateButton.onmouseout = () => {generateButton.className = 'vis-configuration vis-config-button';};

this.optionsContainer = document.createElement('div');
this.optionsContainer.className = 'vis-configuration vis-config-option-container';

this.domElements.push(this.optionsContainer);
this.domElements.push(generateButton);
}
}


Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
],
"main": "./dist/vis.js",
"scripts": {
"test": "mocha --compilers js:babel-core/register",
"test": "nyc mocha --compilers js:babel-core/register",
"test-cov": "nyc --reporter=html mocha --compilers js:babel-core/register",
"build": "gulp",
"lint": "gulp lint",
"watch": "gulp watch",
Expand Down Expand Up @@ -63,7 +64,9 @@
"merge-stream": "^1.0.1",
"mocha": "^3.4.2",
"mocha-jsdom": "^1.1.0",
"nyc": "^11.2.1",
"rimraf": "^2.6.1",
"sinon": "^4.0.1",
"test-console": "^1.0.0",
"uglify-js": "^2.8.29",
"uuid": "^3.1.0",
Expand Down
92 changes: 92 additions & 0 deletions test/Activator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
var assert = require('assert');
var sinon = require('sinon');
var jsdom_global = require('jsdom-global');

var canvasMockify = require('./canvas-mock');
var Activator = require('../lib/shared/Activator');


describe('Activator', function () {
beforeEach(function() {
this.jsdom_global = jsdom_global(
"<div id='mynetwork'></div>",
{ skipWindowCheck: true}
);
canvasMockify(window);
this.container = document.getElementById('mynetwork');
});

afterEach(function() {
this.jsdom_global();
this.container.remove();
this.container = undefined;
});

describe('constructor', function () {

it('sets defaults', function () {
var activator = new Activator(this.container);
assert.equal(activator.active, false);
});

it('creates overlay', function () {
var activator = new Activator(this.container);
assert.equal(activator.dom.container.children[0].className, 'vis-overlay');
});
});

describe('activate', function () {
it('emits an `activate` event', function () {
var eventSpy = sinon.spy();
var activator = new Activator(this.container);
activator.on('activate', eventSpy);
activator.activate();
assert.equal(activator.active, true);
assert(eventSpy.called, 'Event did not fire.');
assert(eventSpy.calledOnce, 'Event fired more than once');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sort of half understand what sinon does, so please humor me: what is this assertion good for?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case sinon provides a spy, which acts as a sort of accumulator of meta data, about how it was used.
Similar to a mock, except that I'm able to verify whether or not it was called, how many times, and what arguments it was called with.
This test ensures that if code changes were introduced that prevented the callback from being triggered, or caused it to be triggered multiple times, the test would fail.

});

it('emits a `change` event', function () {
var eventSpy = sinon.spy();
var activator = new Activator(this.container);
activator.on('change', eventSpy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help noticing in the code that changed is fired every single time that activate() or deactivate() is called.

What about consecutive same calls, e.g. activate(); activate();? I would expect change not to fire because the state has not changed.

If this is not documented, I would consider this faultly. Please give it some thought.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to do about this, but would also consider it outside the scope of this particular issue.

I agree with you, but don't know the impact of making corrective changes.

Would you like me to open an issue to investigate?

activator.activate();
assert.equal(activator.active, true);
assert(eventSpy.called, 'Event did not fire.');
assert(eventSpy.calledOnce, 'Event fired more than once');
});
});

describe('deactivate', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe-block is practically identical to the active block. Any chance of DRY-ing?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think drying it would muddy the test state, or make the tests more complex.

it('emits a `deactivate` event', function () {
var eventSpy = sinon.spy();
var activator = new Activator(this.container);
activator.on('deactivate', eventSpy);
activator.deactivate();
assert.equal(activator.active, false);
assert(eventSpy.called, 'Event did not fire.');
assert(eventSpy.calledOnce, 'Event fired more than once');
});

it('emits a `change` event', function () {
var eventSpy = sinon.spy();
var activator = new Activator(this.container);
activator.on('change', eventSpy);
activator.deactivate();
assert.equal(activator.active, false);
assert(eventSpy.called, 'Event did not fire.');
assert(eventSpy.calledOnce, 'Event fired more than once');
});
});

describe('destroy', function () {

it('sets inactive, removes keycharm, and removes hammer', function () {
var activator = new Activator(this.container);
activator.destroy();
assert.equal(activator.active, false);
assert.equal(activator.keycharm, null);
assert.equal(activator.hammer, null);
});
});
});
123 changes: 123 additions & 0 deletions test/ColorPicker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
var assert = require('assert');
var sinon = require('sinon');
var jsdom_global = require('jsdom-global');

var canvasMockify = require('./canvas-mock');
var ColorPicker = require('../lib/shared/ColorPicker').default;

describe('ColorPicker', function () {
beforeEach(function() {
this.jsdom_global = jsdom_global(
"<div id='mynetwork'></div>",
{ skipWindowCheck: true}
);
canvasMockify(window);
this.container = document.getElementById('mynetwork');
});

afterEach(function() {
this.jsdom_global();
this.container.remove();
this.container = undefined;
});

describe('constructor', function () {

it('sets defaults', function () {
var colorPicker = new ColorPicker();
assert.equal(colorPicker.pixelRatio, 1);
assert.equal(colorPicker.generated, false);
assert.deepEqual(colorPicker.centerCoordinates, {x:289/2, y:289/2});
assert.equal(colorPicker.r, 289 * 0.49);
assert.deepEqual(colorPicker.color, {r:255,g:255,b:255,a:1.0});
assert.equal(colorPicker.hueCircle, undefined);
assert.deepEqual(colorPicker.initialColor, {r:255,g:255,b:255,a:1.0});
assert.equal(colorPicker.previousColor, undefined);
assert.equal(colorPicker.applied, false);
});

// TODO: This gets overridden during instantiation - Is this a bug?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the TODO the reason why it's called xit? Or is that a typo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • initialize() calls _create() which overrides the pixel ratio value if getContext() exists.
  • but if getContext() doesn't exist, you don't need the pixel ratio.

I'd say something fishy is going on here. My current view is that passing the device picel ratio as parameter to the constructor is useless. It might as well get removed (breaking change).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I tried to write the test, could not make it pass, kept the test as documentation of the strange behaviour.

xit('can overwrite default pixelRation', function () {
var colorPicker = new ColorPicker(777);
assert.equal(colorPicker.pixelRatio, 777);
});

});

describe('insertTo', function () {

it('inserts the colorPicker into a div from the DOM', function () {
var colorPicker = new ColorPicker();
colorPicker.insertTo(this.container);
assert.equal(colorPicker.container, this.container);
assert.equal(this.container.children[this.container.children.length-1], colorPicker.frame);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my understanding: does this do a deep compare, or is it just a reference compare?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a deep compare

});
});

describe('setUpdateCallback', function () {

it('prevents non-functions from being set as callback', function () {
var colorPicker = new ColorPicker();
assert.throws(function () {colorPicker.setUpdateCallback(null);}, Error, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to add the usual suspects here: 'null, undefined, [1,2,3], {a: 42}, 42, "meow"`.

Actually, I would consider being able to undo a callback (with null and/or undefined) a feature here.

Same applies to setCloseCallback().

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

});
});

describe('setCloseCallback', function () {

it('prevents non-functions from being set as callback', function () {
var colorPicker = new ColorPicker();
assert.throws(function () {colorPicker.setCloseCallback(null);}, Error, null);
});
});

describe('_hide', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be expecting a test on setting parameter storePrevious to true/false here.


it('runs updateCallback when applied', function () {
var callback = sinon.spy();
var colorPicker = new ColorPicker();
colorPicker.setUpdateCallback(callback);
colorPicker.applied = true;
colorPicker._hide();
assert.equal(callback.callCount, 1);
});

it('does not run updateCallback when not applied', function () {
var callback = sinon.spy();
var colorPicker = new ColorPicker();
colorPicker.setUpdateCallback(callback);
colorPicker.applied = false;
colorPicker._hide();
assert.equal(callback.callCount, 0);
});
});

describe('_isColorString', function () {

it('returns color code when color is found', function () {
var colorPicker = new ColorPicker();
var color = colorPicker._isColorString('black');
assert.equal(color, '#000000');
});

it('returns undefined when color is not found', function () {
var colorPicker = new ColorPicker();
var color = colorPicker._isColorString('zing!');
assert.equal(color, undefined);
});
});

describe('setColor', function () {

it('does not change when \'none\'', function () {
var colorPicker = new ColorPicker();
colorPicker.setColor('none');
assert.deepEqual(colorPicker.color, { r: 255, g: 255, b: 255, a: 1 });
});

it('handles null', function () {
var colorPicker = new ColorPicker();
colorPicker.setColor('none');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is incorrect. Reminder to self: fix this.

assert.deepEqual(colorPicker.color, { r: 255, g: 255, b: 255, a: 1 });
});
});
});
Loading