Commit 71d1957d authored by Pascal Hartig's avatar Pascal Hartig

Merge pull request #668 from passy/flight-test

Unit tests for Flight example
parents 28e16092 afd0bc24
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
'use strict'; 'use strict';
define([ define([
'flight/component', 'flight/lib/component',
'../store' '../store'
], function (defineComponent, dataStore) { ], function (defineComponent, dataStore) {
function stats() { function stats() {
this.defaultAttrs({
dataStore: dataStore
});
this.recount = function () { this.recount = function () {
var todos = dataStore.all(); var todos = this.attr.dataStore.all();
var all = todos.length; var all = todos.length;
var remaining = todos.reduce(function (memo, each) { var remaining = todos.reduce(function (memo, each) {
return memo += each.completed ? 0 : 1; return memo += each.completed ? 0 : 1;
......
...@@ -2,14 +2,17 @@ ...@@ -2,14 +2,17 @@
'use strict'; 'use strict';
define([ define([
'flight/component', 'flight/lib/component',
'../store' '../store'
], function (defineComponent, dataStore) { ], function (defineComponent, dataStore) {
function todos() { function todos() {
var filter; var filter;
this.defaultAttrs({
dataStore: dataStore
});
this.add = function (e, data) { this.add = function (e, data) {
var todo = dataStore.save({ var todo = this.attr.dataStore.save({
title: data.title, title: data.title,
completed: false completed: false
}); });
...@@ -18,7 +21,7 @@ define([ ...@@ -18,7 +21,7 @@ define([
}; };
this.remove = function (e, data) { this.remove = function (e, data) {
var todo = dataStore.destroy(data.id); var todo = this.attr.dataStore.destroy(data.id);
this.trigger('dataTodoRemoved', todo); this.trigger('dataTodoRemoved', todo);
}; };
...@@ -32,15 +35,15 @@ define([ ...@@ -32,15 +35,15 @@ define([
}; };
this.update = function (e, data) { this.update = function (e, data) {
dataStore.save(data); this.attr.dataStore.save(data);
}; };
this.toggleCompleted = function (e, data) { this.toggleCompleted = function (e, data) {
var eventType; var eventType;
var todo = dataStore.get(data.id); var todo = this.attr.dataStore.get(data.id);
todo.completed = !todo.completed; todo.completed = !todo.completed;
dataStore.save(todo); this.attr.dataStore.save(todo);
eventType = filter ? 'dataTodoRemoved' : 'dataTodoToggled'; eventType = filter ? 'dataTodoRemoved' : 'dataTodoToggled';
...@@ -48,7 +51,7 @@ define([ ...@@ -48,7 +51,7 @@ define([
}; };
this.toggleAllCompleted = function (e, data) { this.toggleAllCompleted = function (e, data) {
dataStore.updateAll({ completed: data.completed }); this.attr.dataStore.updateAll({ completed: data.completed });
this.trigger('dataTodoToggledAll', { todos: this.find(filter) }); this.trigger('dataTodoToggledAll', { todos: this.find(filter) });
}; };
...@@ -66,18 +69,18 @@ define([ ...@@ -66,18 +69,18 @@ define([
var todos; var todos;
if (filter) { if (filter) {
todos = dataStore.find(function (each) { todos = this.attr.dataStore.find(function (each) {
return (typeof each[filter] !== 'undefined') ? each.completed : !each.completed; return (typeof each[filter] !== 'undefined') ? each.completed : !each.completed;
}); });
} else { } else {
todos = dataStore.all(); todos = this.attr.dataStore.all();
} }
return todos; return todos;
}; };
this.clearCompleted = function () { this.clearCompleted = function () {
dataStore.destroyAll({ completed: true }); this.attr.dataStore.destroyAll({ completed: true });
this.trigger('uiFilterRequested', { filter: filter }); this.trigger('uiFilterRequested', { filter: filter });
this.trigger('dataClearedCompleted'); this.trigger('dataClearedCompleted');
......
...@@ -6,20 +6,13 @@ require.config({ ...@@ -6,20 +6,13 @@ require.config({
jquery: 'bower_components/jquery/jquery', jquery: 'bower_components/jquery/jquery',
es5shim: 'bower_components/es5-shim/es5-shim', es5shim: 'bower_components/es5-shim/es5-shim',
es5sham: 'bower_components/es5-shim/es5-sham', es5sham: 'bower_components/es5-shim/es5-sham',
text: 'bower_components/requirejs-text/text' text: 'bower_components/requirejs-text/text',
}, flight: 'bower_components/flight',
map: { depot: 'bower_components/depot/depot'
'*': {
'flight/component': 'bower_components/flight/lib/component',
'depot': 'bower_components/depot/depot'
}
}, },
shim: { shim: {
'bower_components/flight/lib/index': {
deps: ['jquery', 'es5shim', 'es5sham']
},
'app/js/app': { 'app/js/app': {
deps: ['bower_components/flight/lib/index'] deps: ['jquery', 'es5shim', 'es5sham']
} }
} }
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define([ define([
'flight/component' 'flight/lib/component'
], function (defineComponent) { ], function (defineComponent) {
function mainSelector() { function mainSelector() {
this.toggle = function (e, data) { this.toggle = function (e, data) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define([ define([
'flight/component' 'flight/lib/component'
], function (defineComponent) { ], function (defineComponent) {
function newItem() { function newItem() {
var ENTER_KEY = 13; var ENTER_KEY = 13;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define([ define([
'flight/component', 'flight/lib/component',
'./with_filters', './with_filters',
'text!app/templates/stats.html', 'text!app/templates/stats.html',
'../utils' '../utils'
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define([ define([
'flight/component', 'flight/lib/component',
'text!app/templates/todo.html', 'text!app/templates/todo.html',
'../utils' '../utils'
], function (defineComponent, todoTmpl, utils) { ], function (defineComponent, todoTmpl, utils) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
'use strict'; 'use strict';
define([ define([
'flight/component' 'flight/lib/component'
], function (defineComponent) { ], function (defineComponent) {
function toggleAll() { function toggleAll() {
this.toggleAllComplete = function () { this.toggleAllComplete = function () {
......
...@@ -3,11 +3,14 @@ ...@@ -3,11 +3,14 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"depot": "~0.1.4", "depot": "~0.1.4",
"es5-shim": "git://github.com/kriskowal/es5-shim.git#2.0.0", "flight": "~1.1.0",
"flight": "~1.0.4",
"jquery": "1.8.3", "jquery": "1.8.3",
"requirejs": "~2.1.5", "requirejs": "~2.1.5",
"todomvc-common": "~0.1.4", "todomvc-common": "~0.1.4",
"requirejs-text": "~2.0.10" "requirejs-text": "~2.0.10"
},
"devDependencies": {
"jasmine-flight": "~2.1.0",
"jasmine-jquery": "~1.5.8"
} }
} }
define(
[
'./utils',
'./registry',
'./debug'
],
function(utils, registry, debug) {
//common mixin allocates basic functionality - used by all component prototypes
//callback context is bound to component
var componentId = 0;
function teardownInstance(instanceInfo){
instanceInfo.events.slice().forEach(function(event) {
var args = [event.type];
event.element && args.unshift(event.element);
(typeof event.callback == 'function') && args.push(event.callback);
this.off.apply(this, args);
}, instanceInfo.instance);
}
function checkSerializable(type, data) {
try {
window.postMessage(data, '*');
} catch(e) {
console.log('unserializable data for event',type,':',data);
throw new Error(
["The event", type, "on component", this.toString(), "was triggered with non-serializable data"].join(" ")
);
}
}
function withBase() {
// delegate trigger, bind and unbind to an element
// if $element not supplied, use component's node
// other arguments are passed on
// event can be either a string specifying the type
// of the event, or a hash specifying both the type
// and a default function to be called.
this.trigger = function() {
var $element, type, data, event, defaultFn;
var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];
if (typeof lastArg != "string" && !(lastArg && lastArg.defaultBehavior)) {
lastIndex--;
data = lastArg;
}
if (lastIndex == 1) {
$element = $(arguments[0]);
event = arguments[1];
} else {
$element = this.$node;
event = arguments[0];
}
if (event.defaultBehavior) {
defaultFn = event.defaultBehavior;
event = $.Event(event.type);
}
type = event.type || event;
if (debug.enabled && window.postMessage) {
checkSerializable.call(this, type, data);
}
if (typeof this.attr.eventData === 'object') {
data = $.extend(true, {}, this.attr.eventData, data);
}
$element.trigger((event || type), data);
if (defaultFn && !event.isDefaultPrevented()) {
(this[defaultFn] || defaultFn).call(this);
}
return $element;
};
this.on = function() {
var $element, type, callback, originalCb;
var lastIndex = arguments.length - 1, origin = arguments[lastIndex];
if (typeof origin == "object") {
//delegate callback
originalCb = utils.delegate(
this.resolveDelegateRules(origin)
);
} else {
originalCb = origin;
}
if (lastIndex == 2) {
$element = $(arguments[0]);
type = arguments[1];
} else {
$element = this.$node;
type = arguments[0];
}
if (typeof originalCb != 'function' && typeof originalCb != 'object') {
throw new Error("Unable to bind to '" + type + "' because the given callback is not a function or an object");
}
callback = originalCb.bind(this);
callback.target = originalCb;
// if the original callback is already branded by jQuery's guid, copy it to the context-bound version
if (originalCb.guid) {
callback.guid = originalCb.guid;
}
$element.on(type, callback);
// get jquery's guid from our bound fn, so unbinding will work
originalCb.guid = callback.guid;
return callback;
};
this.off = function() {
var $element, type, callback;
var lastIndex = arguments.length - 1;
if (typeof arguments[lastIndex] == "function") {
callback = arguments[lastIndex];
lastIndex -= 1;
}
if (lastIndex == 1) {
$element = $(arguments[0]);
type = arguments[1];
} else {
$element = this.$node;
type = arguments[0];
}
return $element.off(type, callback);
};
this.resolveDelegateRules = function(ruleInfo) {
var rules = {};
Object.keys(ruleInfo).forEach(function(r) {
if (!(r in this.attr)) {
throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.');
}
rules[this.attr[r]] = ruleInfo[r];
}, this);
return rules;
};
this.defaultAttrs = function(defaults) {
utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
};
this.select = function(attributeKey) {
return this.$node.find(this.attr[attributeKey]);
};
this.initialize = function(node, attrs) {
attrs = attrs || {};
this.identity = componentId++;
if (!node) {
throw new Error("Component needs a node");
}
if (node.jquery) {
this.node = node[0];
this.$node = node;
} else {
this.node = node;
this.$node = $(node);
}
//merge defaults with supplied options
//put options in attr.__proto__ to avoid merge overhead
var attr = Object.create(attrs);
for (var key in this.defaults) {
if (!attrs.hasOwnProperty(key)) {
attr[key] = this.defaults[key];
}
}
this.attr = attr;
Object.keys(this.defaults || {}).forEach(function(key) {
if (this.defaults[key] === null && this.attr[key] === null) {
throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".');
}
}, this);
return this;
}
this.teardown = function() {
teardownInstance(registry.findInstanceInfo(this));
};
}
return withBase;
});
...@@ -12,31 +12,15 @@ define( ...@@ -12,31 +12,15 @@ define(
'./advice', './advice',
'./utils', './utils',
'./compose', './compose',
'./base',
'./registry', './registry',
'./logger', './logger',
'../tools/debug/debug' './debug'
], ],
function(advice, utils, compose, registry, withLogging, debug) { function(advice, utils, compose, withBase, registry, withLogging, debug) {
var functionNameRegEx = /function (.*?)\s?\(/; var functionNameRegEx = /function (.*?)\s?\(/;
var componentId = 0;
function teardownInstance(instanceInfo){
instanceInfo.events.slice().forEach(function(event) {
var args = [event.type];
event.element && args.unshift(event.element);
(typeof event.callback == 'function') && args.push(event.callback);
this.off.apply(this, args);
}, instanceInfo.instance);
}
function teardown() {
teardownInstance(registry.findInstanceInfo(this));
}
//teardown for all instances of this constructor //teardown for all instances of this constructor
function teardownAll() { function teardownAll() {
...@@ -59,143 +43,6 @@ define( ...@@ -59,143 +43,6 @@ define(
} }
} }
//common mixin allocates basic functionality - used by all component prototypes
//callback context is bound to component
function withBaseComponent() {
// delegate trigger, bind and unbind to an element
// if $element not supplied, use component's node
// other arguments are passed on
// event can be either a string specifying the type
// of the event, or a hash specifying both the type
// and a default function to be called.
this.trigger = function() {
var $element, type, data, event, defaultFn;
var lastIndex = arguments.length - 1, lastArg = arguments[lastIndex];
if (typeof lastArg != "string" && !(lastArg && lastArg.defaultBehavior)) {
lastIndex--;
data = lastArg;
}
if (lastIndex == 1) {
$element = $(arguments[0]);
event = arguments[1];
} else {
$element = this.$node;
event = arguments[0];
}
if (event.defaultBehavior) {
defaultFn = event.defaultBehavior;
event = $.Event(event.type);
}
type = event.type || event;
if (debug.enabled && window.postMessage) {
checkSerializable.call(this, type, data);
}
if (typeof this.attr.eventData === 'object') {
data = $.extend(true, {}, this.attr.eventData, data);
}
$element.trigger((event || type), data);
if (defaultFn && !event.isDefaultPrevented()) {
(this[defaultFn] || defaultFn).call(this);
}
return $element;
};
this.on = function() {
var $element, type, callback, originalCb;
var lastIndex = arguments.length - 1, origin = arguments[lastIndex];
if (typeof origin == "object") {
//delegate callback
originalCb = utils.delegate(
this.resolveDelegateRules(origin)
);
} else {
originalCb = origin;
}
if (lastIndex == 2) {
$element = $(arguments[0]);
type = arguments[1];
} else {
$element = this.$node;
type = arguments[0];
}
if (typeof originalCb != 'function' && typeof originalCb != 'object') {
throw new Error("Unable to bind to '" + type + "' because the given callback is not a function or an object");
}
callback = originalCb.bind(this);
callback.target = originalCb;
// if the original callback is already branded by jQuery's guid, copy it to the context-bound version
if (originalCb.guid) {
callback.guid = originalCb.guid;
}
$element.on(type, callback);
// get jquery's guid from our bound fn, so unbinding will work
originalCb.guid = callback.guid;
return callback;
};
this.off = function() {
var $element, type, callback;
var lastIndex = arguments.length - 1;
if (typeof arguments[lastIndex] == "function") {
callback = arguments[lastIndex];
lastIndex -= 1;
}
if (lastIndex == 1) {
$element = $(arguments[0]);
type = arguments[1];
} else {
$element = this.$node;
type = arguments[0];
}
return $element.off(type, callback);
};
this.resolveDelegateRules = function(ruleInfo) {
var rules = {};
Object.keys(ruleInfo).forEach(function(r) {
if (!r in this.attr) {
throw new Error('Component "' + this.toString() + '" wants to listen on "' + r + '" but no such attribute was defined.');
}
rules[this.attr[r]] = ruleInfo[r];
}, this);
return rules;
};
this.defaultAttrs = function(defaults) {
utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
};
this.select = function(attributeKey) {
return this.$node.find(this.attr[attributeKey]);
};
this.initialize = $.noop;
this.teardown = teardown;
}
function attachTo(selector/*, options args */) { function attachTo(selector/*, options args */) {
// unpacking arguments by hand benchmarked faster // unpacking arguments by hand benchmarked faster
var l = arguments.length; var l = arguments.length;
...@@ -216,7 +63,7 @@ define( ...@@ -216,7 +63,7 @@ define(
return; return;
} }
new this(node, options); (new this).initialize(node, options);
}.bind(this)); }.bind(this));
} }
...@@ -229,64 +76,26 @@ define( ...@@ -229,64 +76,26 @@ define(
var mixins = new Array(l + 3); //add three for common mixins var mixins = new Array(l + 3); //add three for common mixins
for (var i = 0; i < l; i++) mixins[i] = arguments[i]; for (var i = 0; i < l; i++) mixins[i] = arguments[i];
Component.toString = function() { var Component = function() {};
Component.toString = Component.prototype.toString = function() {
var prettyPrintMixins = mixins.map(function(mixin) { var prettyPrintMixins = mixins.map(function(mixin) {
if (mixin.name == null) { if (mixin.name == null) {
//function name property not supported by this browser, use regex //function name property not supported by this browser, use regex
var m = mixin.toString().match(functionNameRegEx); var m = mixin.toString().match(functionNameRegEx);
return (m && m[1]) ? m[1] : ""; return (m && m[1]) ? m[1] : "";
} else { } else {
return (mixin.name != "withBaseComponent") ? mixin.name : ""; return (mixin.name != "withBase") ? mixin.name : "";
} }
}).filter(Boolean).join(', '); }).filter(Boolean).join(', ');
return prettyPrintMixins; return prettyPrintMixins;
}; };
if (debug.enabled) { if (debug.enabled) {
Component.describe = Component.toString(); Component.describe = Component.prototype.describe = Component.toString();
} }
//'options' is optional hash to be merged with 'defaults' in the component definition //'options' is optional hash to be merged with 'defaults' in the component definition
function Component(node, options) {
options = options || {};
this.identity = componentId++;
if (!node) {
throw new Error("Component needs a node");
}
if (node.jquery) {
this.node = node[0];
this.$node = node;
} else {
this.node = node;
this.$node = $(node);
}
this.toString = Component.toString;
if (debug.enabled) {
this.describe = this.toString();
}
//merge defaults with supplied options
//put options in attr.__proto__ to avoid merge overhead
var attr = Object.create(options);
for (var key in this.defaults) {
if (!options.hasOwnProperty(key)) {
attr[key] = this.defaults[key];
}
}
this.attr = attr;
Object.keys(this.defaults || {}).forEach(function(key) {
if (this.defaults[key] === null && this.attr[key] === null) {
throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.toString() + '".');
}
}, this);
this.initialize.call(this, options);
}
Component.attachTo = attachTo; Component.attachTo = attachTo;
Component.teardownAll = teardownAll; Component.teardownAll = teardownAll;
...@@ -294,7 +103,7 @@ define( ...@@ -294,7 +103,7 @@ define(
if (debug.enabled) { if (debug.enabled) {
mixins.unshift(withLogging); mixins.unshift(withLogging);
} }
mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration); mixins.unshift(withBase, advice.withAdvice, registry.withRegistration);
compose.mixin(Component.prototype, mixins); compose.mixin(Component.prototype, mixins);
return Component; return Component;
......
...@@ -10,7 +10,7 @@ define( ...@@ -10,7 +10,7 @@ define(
[ [
'./utils', './utils',
'../tools/debug/debug' './debug'
], ],
function(util, debug) { function(util, debug) {
......
"use strict"; "use strict";
define( define([], function() {
[
'../../lib/registry',
'../../lib/utils'
],
function(registry, utils) {
var logFilter; var logFilter;
......
...@@ -207,7 +207,7 @@ define( ...@@ -207,7 +207,7 @@ define(
}; };
this.withRegistration = function() { this.withRegistration = function() {
this.before('initialize', function() { this.after('initialize', function() {
registry.addInstance(this); registry.addInstance(this);
}); });
......
// Karma configuration file
//
// For all available config options and default values, see:
// https://github.com/karma-runner/karma/blob/stable/lib/config.js#L54
module.exports = function (config) {
'use strict';
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
// loaded without require
'bower_components/es5-shim/es5-shim.js',
'bower_components/es5-shim/es5-sham.js',
'bower_components/jquery/jquery.js',
'bower_components/jasmine-flight/lib/jasmine-flight.js',
'bower_components/jasmine-jquery/lib/jasmine-jquery.js',
// hack to load RequireJS after the shim libs
'node_modules/karma-requirejs/lib/require.js',
'node_modules/karma-requirejs/lib/adapter.js',
// loaded with require
{ pattern: 'bower_components/flight/**/*.js', included: false },
{ pattern: 'bower_components/depot/**/*.js', included: false },
{ pattern: 'bower_components/requirejs-text/text.js', included: false },
{ pattern: 'app/**/*.js', included: false },
{ pattern: 'app/**/*.html', included: false },
{ pattern: 'test/spec/**/*_spec.js', included: false },
{ pattern: 'test/fixture/*.html', included: false },
// Entry point for karma.
'test/test-main.js',
{ pattern: 'test/mock/*.js', included: true }
],
// list of files to exclude
exclude: [],
// use dots reporter, as travis terminal does not support escaping sequences
// possible values: 'dots', 'progress'
// CLI --reporters progress
reporters: ['dots'],
// enable / disable watching file and executing tests whenever any file changes
// CLI --auto-watch --no-auto-watch
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
// CLI --browsers Chrome, Firefox, Safari
browsers: ['Chrome', 'Firefox'],
// If browser does not capture in given timeout [ms], kill it
// CLI --capture-timeout 5000
captureTimeout: 20000,
// Auto run tests on start (when browsers are captured) and exit
// CLI --single-run --no-single-run
singleRun: false,
plugins: [
'karma-jasmine',
'karma-requirejs',
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-ie-launcher',
'karma-phantomjs-launcher',
'karma-safari-launcher'
]
});
};
{
"name": "flight-todomvc",
"version": "0.0.0",
"devDependencies": {
"karma": "~0.10.1",
"karma-jasmine": "~0.1.0",
"karma-requirejs": "~0.1.0",
"karma-chrome-launcher": "~0.1.0",
"karma-ie-launcher": "~0.1.1",
"karma-firefox-launcher": "~0.1.0",
"karma-phantomjs-launcher": "~0.1.0",
"karma-safari-launcher": "~0.1.1"
},
"scripts": {
"test": "karma start --browsers Firefox --single-run"
}
}
...@@ -17,3 +17,17 @@ Here are some links you may find helpful: ...@@ -17,3 +17,17 @@ Here are some links you may find helpful:
* [Flight on Twitter](http://twitter.com/flight) * [Flight on Twitter](http://twitter.com/flight)
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
## Running the Tests
To run the tests, you need to install additional helpers via bower and npm and
run `npm test` afterwards:
```bash
bower install & npm install
npm test
```
You can also run `karma start` after the dependencies are installed to
automatically watch for changes and re-run the test suite.
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"white": true,
"jquery": true,
"globals": {
"requirejs": false,
"describe": false,
"describeComponent": false,
"setupComponent": false,
"it": false,
"expect": false,
"beforeEach": false,
"afterEach": false,
"spyOnEvent": false,
"mocks": false,
"readFixtures": false,
"spyOn": false
}
}
<input id="new-todo" placeholder="What needs to be done?" autofocus>
(function () {
'use strict';
function FakeDataStore(data) {
this.data = data || [
{title: 'buy some unicorns', completed: false, id: '108e8c51-bb3f-608d-15e2-611cfc7e5838'},
{title: 'get a baguette mustache', completed: false, id: '6cdd0a1f-b6f8-21e1-f35b-aa18af99ad91'},
{title: 'shave yaks', completed: true, id: 'abe678dd-3087-198c-eefa-180248cb3d8b'}
];
}
FakeDataStore.prototype.all = function all() {
return this.data;
};
FakeDataStore.prototype.save = function save(data) {
this.data.push(data);
};
FakeDataStore.prototype.destroyAll = function destroyAll() {
// Stub
};
window.mocks.DataStore = FakeDataStore;
}());
describeComponent('app/js/data/stats', function () {
'use strict';
describe('recount without datastore', function () {
beforeEach(function () {
setupComponent({
dataStore: new mocks.DataStore([])
});
});
afterEach(function () {
localStorage.clear();
});
it('should trigger a dataStatsCounted event', function () {
spyOnEvent(document, 'dataStatsCounted');
this.component.recount();
expect('dataStatsCounted').toHaveBeenTriggeredOn(document);
});
it('should trigger dataStatsCounted when todos are loaded', function () {
spyOnEvent(document, 'dataStatsCounted');
$(document).trigger('dataTodosLoaded');
expect('dataStatsCounted').toHaveBeenTriggeredOn(document);
});
it('should provide empty stats', function () {
spyOnEvent(document, 'dataStatsCounted');
this.component.recount();
expect('dataStatsCounted').toHaveBeenTriggeredOnAndWith(document, {
all: 0,
remaining: 0,
completed: 0,
filter: ''
});
});
});
describe('recount with datastore', function () {
beforeEach(function () {
setupComponent({
dataStore: new mocks.DataStore()
});
});
it('should provide full stats', function () {
spyOnEvent(document, 'dataStatsCounted');
this.component.recount();
expect('dataStatsCounted').toHaveBeenTriggeredOnAndWith(document, {
all: 3,
remaining: 2,
completed: 1,
filter: ''
});
});
});
});
describeComponent('app/js/data/todos', function () {
'use strict';
describe('without datastore', function () {
beforeEach(function () {
this.dataStore = new mocks.DataStore([]);
setupComponent({
dataStore: this.dataStore
});
});
it('should add a new entry', function () {
var title = 'buy some unicorns';
spyOnEvent(document, 'dataTodoAdded');
this.component.trigger('uiAddRequested', {
title: title
});
expect('dataTodoAdded').toHaveBeenTriggeredOn(document);
expect(this.dataStore.data.length).toBe(1);
expect(this.dataStore.all()[0].title).toBe(title);
});
});
describe('with datastore', function () {
beforeEach(function () {
this.dataStore = new mocks.DataStore();
setupComponent({
dataStore: this.dataStore
});
});
it('removes completed', function () {
spyOn(this.dataStore, 'destroyAll');
this.component.trigger('uiClearRequested');
expect(this.dataStore.destroyAll).toHaveBeenCalledWith({ completed: true });
});
});
});
describeComponent('app/js/ui/new_item', function () {
'use strict';
var ENTER_KEY = 13;
beforeEach(function () {
setupComponent(readFixtures('new_todo.html'));
});
it('triggers uiAddRequested on enter', function () {
var event = $.Event('keydown');
event.which = ENTER_KEY;
spyOnEvent(document, 'uiAddRequested');
this.component.$node.val('shave moar yaks');
this.component.trigger(event);
expect('uiAddRequested').toHaveBeenTriggeredOnAndWith(document, {
title: 'shave moar yaks'
});
});
it('trims values', function () {
var event = $.Event('keydown');
event.which = ENTER_KEY;
spyOnEvent(document, 'uiAddRequested');
this.component.$node.val(' trim inputs ');
this.component.trigger(event);
expect('uiAddRequested').toHaveBeenTriggeredOnAndWith(document, {
title: 'trim inputs'
});
});
it('ignore empty values', function () {
var event = $.Event('keydown');
event.which = ENTER_KEY;
spyOnEvent(document, 'uiAddRequested');
this.component.trigger(event);
expect('uiAddRequested').not.toHaveBeenTriggeredOn(document);
});
});
describeComponent('app/js/ui/stats', function () {
'use strict';
beforeEach(function () {
setupComponent(readFixtures('footer.html'));
});
it('renders when stats change', function () {
var data = {
all: 3,
remaining: 2,
completed: 1,
filter: ''
};
expect(this.component.$node).toBeEmpty();
$(document).trigger('dataStatsCounted', data);
expect(this.component.$node).not.toBeEmpty();
expect(this.component.$node).toBeVisible();
});
it('is hidden when data is empty', function () {
var data = {
all: 0,
remaining: 0,
completed: 0,
filter: ''
};
$(document).trigger('dataStatsCounted', data);
expect(this.component.$node).toBeHidden();
});
it('hides clear-completed with no completed items', function () {
var data = {
all: 2,
remaining: 2,
completed: 0,
filter: ''
};
this.component.render(null, data);
expect(this.component.select('clearCompletedSelector').length).toBe(0);
});
it('shows clear-completed with completed items', function () {
var data = {
all: 2,
remaining: 1,
completed: 1,
filter: ''
};
this.component.render(null, data);
expect(this.component.select('clearCompletedSelector').length).toBe(1);
});
it('triggers uiClearRequested on click', function () {
var data = {
all: 2,
remaining: 1,
completed: 1,
filter: ''
};
spyOnEvent(document, 'uiClearRequested');
this.component.render(null, data);
this.component.trigger(this.component.attr.clearCompletedSelector, 'click');
expect('uiClearRequested').toHaveBeenTriggeredOn(this.component);
});
});
/*jshint camelcase:false*/
/*global jasmine*/
'use strict';
var tests = Object.keys(window.__karma__.files).filter(function (file) {
return (/_spec\.js$/.test(file));
});
window.mocks = {};
requirejs.config({
// Karma serves files from '/base'
baseUrl: '/base',
paths: {
flight: 'bower_components/flight',
depot: 'bower_components/depot/depot',
text: 'bower_components/requirejs-text/text'
},
// ask Require.js to load these files (all our tests)
deps: tests,
// start test run, once Require.js is done
callback: window.__karma__.start
});
jasmine.getFixtures().fixturesPath = '/base/test/fixture';
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment