Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #7 from RStankov/extract-todos-module

Extract todos module
  • Loading branch information...
commit 63020e20f7bb49b03d4c5a41731016eaf29f7c3c 2 parents 530ad51 + 24d594f
@vesln authored
View
6 lib/cli.js
@@ -10,14 +10,14 @@
*
* @type {Object}
*/
-var app = module.exports = require('./app')
+var app = module.exports = require('./app');
/**
* Commands.
*
* @type {Object}
*/
-var commands = require('./commands');
+var commands = require('./commands').init(require('./todos').init(require('./storage')));
// Version.
app.cmd(/version/, commands.version);
@@ -35,7 +35,7 @@ app.cmd(/check (.+)/, commands.check);
app.cmd(/undo (.+)/, commands.undo);
// Removes a todo item.
-app.cmd(/rm (.+)/, commands.delete);
+app.cmd(/rm (.+)/, commands.destroy);
// Writes todo to file.
app.cmd(/write (.+)/, commands.write);
View
124 lib/commands.js
@@ -17,28 +17,32 @@ var commands = module.exports;
*
* @api private
*/
-commands.print = console.log;
+commands.print = function(value) {
+ console.log(value);
+};
/**
- * The application.
+ * Format items for print.
*
- * @type {Object}
+ * @api private
*/
-var app = require('./app');
+commands.format = function(items) {
+ return "\n" + items.map(format).join("\n") + "\n";
+};
/**
- * Storage. Just an alias to application config.
+ * The application.
*
* @type {Object}
*/
-var storage = require('./storage');
+var app = require('./app');
/**
* Formatter.
*
* @type {Object}
*/
-var formatter = require('./formatter');
+var format = require('./formatter').format;
/**
* File writing.
@@ -46,6 +50,16 @@ var formatter = require('./formatter');
var fs = require('fs');
/**
+ * Initializes the commands by given todos.
+ *
+ * @api public
+ */
+commands.init = function(todos) {
+ commands.todos = todos;
+ return commands;
+}
+
+/**
* Prints current version.
*
* @api public
@@ -60,18 +74,8 @@ commands.version = function() {
* @api public
*/
commands.list = function() {
- var out = [];
- storage.get('items', function(err, items) {
- items || (items = []);
- for (var i = -1, len = items.length; ++i < len;) {
- if (!app.argv.all && items[i].done) continue;
- out.push(formatter.format(items[i], i + 1));
- }
-
- out.push('') && out.unshift('');
- out.map(function(line) {
- commands.print(line);
- });
+ commands.todos[app.argv.all ? 'all' : 'undone'](function(items) {
+ commands.print(commands.format(items));
});
};
@@ -82,8 +86,7 @@ commands.list = function() {
* @api public
*/
commands.check = function(num) {
- num = +num - 1;
- commands.toggle(num, true);
+ commands.todos.check(+num - 1, true);
};
/**
@@ -93,28 +96,7 @@ commands.check = function(num) {
* @api public
*/
commands.undo = function(num) {
- num = +num - 1;
- commands.toggle(num, false);
-};
-
-/**
- * Toggles an item state.
- *
- * @param {Number} Item index.
- * @param {Boolean} State.
- * @api private
- */
-commands.toggle = function(num, state) {
- storage.get('items', function(err, items) {
- items || (items = []);
- if (!items[num]) throw new Error('There is no todo item with number ' + num + 1);
- items[num].done = state;
- storage.set('items', items, function() {
- storage.save(function(err) {
- if (err) throw err;
- });
- });
- });
+ commands.todos.check(+num - 1, false);
};
/**
@@ -123,17 +105,8 @@ commands.toggle = function(num, state) {
* @param {String} Todo item number.
* @api public
*/
-commands.delete = function(num) {
- num = +num - 1;
- storage.get('items', function(err, items) {
- items || (items = []);
- items.splice(num, 1);
- storage.set('items', items, function() {
- storage.save(function(err) {
- if (err) throw err;
- });
- });
- });
+commands.destroy = function(num) {
+ commands.todos.destroy(+num - 1);
};
/**
@@ -143,11 +116,7 @@ commands.delete = function(num) {
* @api public
*/
commands.clear = function(num) {
- storage.set('items', [], function(err, items) {
- storage.save(function(err) {
- if (err) throw err;
- });
- });
+ commands.todos.clear();
};
/**
@@ -157,15 +126,7 @@ commands.clear = function(num) {
* @api public
*/
commands.add = function(item) {
- storage.get('items', function(err, items) {
- items || (items = []);
- items.push({ text: item, done: false });
- storage.set('items', items, function(err) {
- storage.save(function(err) {
- if (err) throw err;
- });
- });
- });
+ commands.todos.add(item);
};
/**
@@ -175,26 +136,15 @@ commands.add = function(item) {
* @api public
*/
commands.write = function(filename) {
- var data = '';
- if (!filename) {
- var filename = "~/todo.txt";
- }
- var out = [];
- storage.get('items', function(err, items) {
- items || (items = []);
- for (var i = -1, len = items.length; ++i < len;) {
- if (!app.argv.all && items[i].done) continue;
- out.push(formatter.format(items[i], i + 1));
- }
-
- out.push('') && out.unshift('');
- out.map(function(line) {
- console.log(line);
- data += line +"\n";
- });
- fs.writeFile(filename, data, 'utf8', function(err, written) {
- if (err) return console.log(err);
+ filename || (filename = "~/todo.txt");
+
+ commands.todos[app.argv.all ? 'all' : 'undone'](function(items) {
+ var data = commands.format(items);
+ commands.print(data);
+
+ fs.writeFile(filename, data, 'utf8', function(err, written) {
+ if (err) return commands.print(err);
});
});
};
View
2  lib/formatter.js
@@ -29,7 +29,7 @@ formatter.format = function(item, num) {
var state = (item.done) ? ''.green : ''.red;
return ' '
- + '#' + num + ' '
+ + '#' + (num + 1) + ' '
+ state + ' '
+ item.text;
};
View
60 lib/todos.js
@@ -0,0 +1,60 @@
+var Todos = module.exports = function(storage) {
+ this.storage = storage;
+};
+
+Todos.init = function(storage) {
+ return new Todos(storage);
+};
+
+Todos.prototype.all = function(callback) {
+ this.storage.get('items', function(err, items) {
+ callback(items || []);
+ });
+};
+
+Todos.prototype.add = function(text) {
+ this.all(function(items) {
+ items.push({text: text, done: false});
+
+ this._update(items);
+ }.bind(this));
+};
+
+Todos.prototype.destroy = function(num) {
+ this.all(function(items) {
+ items.splice(num, 1);
+
+ this._update(items);
+ }.bind(this));
+};
+
+Todos.prototype.check = function(num, checked) {
+ this.all(function(items) {
+ if (!items[num]) {
+ throw new Error('There is no todo item with number ' + num + 1);
+ }
+
+ items[num].done = checked;
+
+ this._update(items);
+ }.bind(this));
+};
+
+Todos.prototype.undone = function(callback) {
+ this.all(function(items) {
+ items = items.filter(function(item) { return !item.done; });
+ callback(items);
+ });
+};
+
+Todos.prototype.clear = function() {
+ this._update([]);
+};
+
+Todos.prototype._update = function(items) {
+ this.storage.set('items', items, function() {
+ this.storage.save(function(err) {
+ if (err) throw err;
+ });
+ }.bind(this));
+};
View
2  package.json
@@ -11,7 +11,7 @@
}
, "devDependencies": {
"mocha": "0.3.3"
- , "should": "0.3.2"
+ , "should": "0.5.1"
, "sinon": "1.3.1"
}
, "repository" : {
View
8 test/cli.test.js
@@ -1,6 +1,6 @@
/*!
* todo - Todos in the CLI like what.
- *
+ *
* Veselin Todorov <hi@vesln.com>
* MIT License.
*/
@@ -13,7 +13,7 @@ var path = require('path');
/**
* The tests object.
- *
+ *
* @type {Object}
*/
var cli = require('../lib/cli');
@@ -28,12 +28,12 @@ describe('cli', function() {
it('should expose flatiron app', function() {
cli.should.eql(flatiron.app);
});
-
+
it('should register routes', function() {
cli.router.routes.version.on.should.eql(commands.version);
cli.router.routes.ls.on.should.eql(commands.list);
cli.router.routes.clear.on.should.eql(commands.clear);
- cli.router.routes.rm['(.+)'].on.should.eql(commands.delete);
+ cli.router.routes.rm['(.+)'].on.should.eql(commands.destroy);
cli.router.routes.check['(.+)'].on.should.eql(commands.check);
cli.router.routes.undo['(.+)'].on.should.eql(commands.undo);
cli.router.routes['(.+)'].on.should.eql(commands.add);
View
187 test/commands.test.js
@@ -1,153 +1,90 @@
/*!
* todo - Todos in the CLI like what.
- *
+ *
* Veselin Todorov <hi@vesln.com>
* MIT License.
*/
-
-/**
- * Module dependencies.
- */
-var sinon = require('sinon');
-
-/**
- * Support.
- */
-var storage = require('../lib/storage');
-/**
- * The tests object.
- *
- * @type {Object}
- */
-var commands = require('../lib/commands');
+var sinon = require('sinon'),
+ init = require('../lib/commands').init,
+ Todos = require('../lib/todos'),
+ Storage = require('./support/fake_storage');
describe('commands', function() {
- describe('.version()', function() {
- it('should be sane', function() {
- commands.version.should.be.ok
+ var storage, commands;
+
+ beforeEach(function() {
+ storage = new Storage();
+ commands = init(new Todos(storage));
+ });
+
+ describe('version', function() {
+ it('is sane', function() {
+ commands.version.should.be.ok;
});
});
-
- describe('.list()', function() {
- it('should return list items', function(done) {
- var i = 0;
- var expected = 3;
- var out = '';
-
- sinon.stub(storage, 'get', function(key, cb) {
- cb(null, [{text: 'Foo', done: false}]);
- });
-
- sinon.stub(commands, 'print', function(text) {
- out += text;
- if (++i === expected) finish();
- });
-
- function finish() {
- out.should.eql(' #1 \u001b[31m✖\u001b[39m Foo');
- commands.print.restore();
- storage.get.restore();
+
+ describe('list', function() {
+ it('print list items', function(done) {
+ storage.items = [{text: 'Foo', done: false}];
+
+ sinon.stub(console, 'log', function(text) {
+ text.should.eql("\n" + ' #1 \u001b[31m✖\u001b[39m Foo' + "\n");
+ console.log.restore();
done();
- };
-
+ });
+
commands.list();
});
});
-
- describe('.check()', function(done) {
- it('should mark todo item as done', function(done) {
- sinon.stub(storage, 'get', function(key, cb) {
- cb(null, [{text: 'Foo', done: false}]);
- });
- sinon.stub(storage, 'set', function(key, value, cb) {
- key.should.eql('items');
- value.should.eql([ { text: 'Foo', done: true } ]);
- cb();
- });
- sinon.stub(storage, 'save', function(key, cb) {
- storage.set.restore();
- storage.get.restore();
- storage.save.restore();
- done();
- });
+
+ describe('check', function() {
+ it('marks todo item as done', function() {
+ storage.items = [{done: false}];
+
commands.check(1);
+
+ storage.items.should.eql([{done: true}]);
});
});
-
- describe('.undo()', function(done) {
- it('should mark todo item as not done yet', function(done) {
- sinon.stub(storage, 'get', function(key, cb) {
- cb(null, [{text: 'Foo', done: true}]);
- });
- sinon.stub(storage, 'set', function(key, value, cb) {
- key.should.eql('items');
- value.should.eql([ { text: 'Foo', done: false } ]);
- cb();
- });
- sinon.stub(storage, 'save', function(key, cb) {
- storage.set.restore();
- storage.get.restore();
- storage.save.restore();
- done();
- });
+
+ describe('undo', function() {
+ it('marks todo item as not done yet', function() {
+ storage.items = [{done: true}];
+
commands.undo(1);
+
+ storage.items.should.eql([{done: false}]);
});
});
-
- describe('.delete()', function(done) {
- it('should remove an item', function(done) {
- sinon.stub(storage, 'get', function(key, cb) {
- cb(null, [{text: 'Foo', done: true}]);
- });
- sinon.stub(storage, 'set', function(key, value, cb) {
- key.should.eql('items');
- value.should.eql([]);
- cb();
- });
- sinon.stub(storage, 'save', function(key, cb) {
- storage.set.restore();
- storage.get.restore();
- storage.save.restore();
- done();
- });
- commands.delete(1);
+
+ describe('destroy', function() {
+ it('removes an item', function() {
+ storage.items = [1, 2];
+
+ commands.destroy(1);
+
+ storage.items.should.eql([2]);
});
});
-
- describe('.add()', function(done) {
- it('should remove an item', function(done) {
- sinon.stub(storage, 'get', function(key, cb) {
- cb(null, [{text: 'Foo', done: true}]);
- });
- sinon.stub(storage, 'set', function(key, value, cb) {
- key.should.eql('items');
- value.should.eql([{text: 'Foo', done: true}, {text: 'Foo bar', done: false}]);
- cb();
- });
- sinon.stub(storage, 'save', function(key, cb) {
- storage.set.restore();
- storage.get.restore();
- storage.save.restore();
- done();
- });
- commands.add('Foo bar');
+
+ describe('add', function() {
+ it('adds an item', function() {
+ storage.items = [1];
+
+ commands.add("Foo bar");
+
+ storage.items[1].should.eql({text: 'Foo bar', done: false});
});
});
-
- describe('.clear()', function(done) {
- it('should clear the todo', function(done) {
- sinon.stub(storage, 'set', function(key, value, cb) {
- key.should.eql('items');
- value.should.eql([]);
- cb();
- });
- sinon.stub(storage, 'save', function(key, cb) {
- storage.set.restore();
- storage.save.restore();
- done();
- });
+
+ describe('clear', function() {
+ it('clear all todos', function() {
+ storage.items = [1,2,3,4];
+
commands.clear();
+
+ storage.items.should.eql([]);
});
});
});
View
4 test/formatter.test.js
@@ -15,11 +15,11 @@ var formatter = require('../lib/formatter');
describe('formatter', function() {
describe('.format()', function() {
it('should format done items properly', function() {
- formatter.format({ text: 'Foo', done: true }, 1).should.eql(' #1 \u001b[32m√\u001b[39m Foo');
+ formatter.format({ text: 'Foo', done: true }, 0).should.eql(' #1 \u001b[32m√\u001b[39m Foo');
});
it('should format unfinished items properly', function() {
- formatter.format({ text: 'Foo', done: false }, 1).should.eql(' #1 \u001b[31m✖\u001b[39m Foo');
+ formatter.format({ text: 'Foo', done: false }, 0).should.eql(' #1 \u001b[31m✖\u001b[39m Foo');
});
});
});
View
25 test/support/fake_storage.js
@@ -0,0 +1,25 @@
+var Storage = module.exports = function() {};
+
+Storage.prototype.get = function(type, callback) {
+ if (type != 'items') {
+ throw 'Invalid type - ' + type + ' (should be items)';
+ }
+
+ callback(null, this.items);
+};
+
+Storage.prototype.set = function(type, items, callback) {
+ if (type != 'items') {
+ throw 'Invalid type - ' + type + ' (should be items)';
+ }
+
+ this._items = items;
+
+ callback(null);
+};
+
+Storage.prototype.save = function(callback) {
+ this.items = this._items;
+ delete this._items;
+ callback(null);
+};
View
93 test/todos.test.js
@@ -0,0 +1,93 @@
+var Todos = require('../lib/todos'),
+ Storage = require('./support/fake_storage');
+
+describe("todos", function() {
+ var fake, todos, items;
+
+ beforeEach(function() {
+ fake = new Storage();
+ todos = new Todos(fake);
+ items = function(method) {
+ var returned;
+ todos[method](function(items) { returned = items; });
+ return returned;
+ };
+ });
+
+ describe("all", function() {
+ it("runs a callback with all items", function() {
+ fake.items = [1,2,3,4,5];
+
+ items('all').should.eql([1,2,3,4,5]);
+ });
+
+ it("runs a callback with empty array if no items are preset", function() {
+ fake.items = null;
+
+ items('all').should.eql([]);
+ });
+ });
+
+ describe("add", function() {
+ it("adds an todo items", function() {
+ todos.add("Text");
+
+ var todo = items('all')[0];
+
+ todo.text.should.eql("Text");
+ todo.done.should.not.be.ok;
+ });
+ });
+
+ describe("destroy", function() {
+ it("removes an item with given index", function() {
+ fake.items = [1,2,3,4,5];
+
+ todos.destroy(1);
+
+ items('all').should.eql([1,3,4,5]);
+ });
+ });
+
+ describe("check", function() {
+ it("can mark todo as done", function() {
+ fake.items = [{done: false}];
+
+ todos.check(0, true);
+
+ items('all')[0].done.should.be.ok;
+ });
+
+ it("can mark todo as no done", function() {
+ fake.items = [{done: true}];
+
+ todos.check(0, false);
+
+ items('all')[0].done.should.not.be.ok;
+ });
+
+ it("throws an error if todo doesn't not exits", function() {
+ (function () { todos.check(0, false); }).should.throw(/There is no todo item with number/);
+ });
+ });
+
+ describe("clear", function() {
+ it("removes all items", function() {
+ fake.items = [1,2,3];
+
+ todos.clear();
+
+ items('all').should.eql([]);
+ });
+ });
+
+ describe("undone", function() {
+ it("runs a callback with all undone items", function() {
+ var done = {done: true},
+ undone = {done: false};
+
+ fake.items = [done, undone, done, undone];
+ items('undone').should.eql([undone, undone]);
+ });
+ });
+});
Please sign in to comment.
Something went wrong with that request. Please try again.