diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..0d258d23 --- /dev/null +++ b/.bowerrc @@ -0,0 +1 @@ +{ "directory": "public/bower_components" } diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c346b134 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bower_components/ +node_modules/ diff --git a/.jshintrc b/.jshintrc index cd595a55..db9b48e0 100644 --- a/.jshintrc +++ b/.jshintrc @@ -72,13 +72,13 @@ "couch" : false, // CouchDB "devel" : true, // Development/debugging (alert, confirm, etc) "dojo" : false, // Dojo Toolkit - "jasmine" : false, // Jasmine + "jasmine" : true, // Jasmine "jquery" : false, // jQuery "mocha" : true, // Mocha "mootools" : false, // MooTools "node" : false, // Node.js "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) - "phantom" : false, // PhantomJS + "phantom" : true, // PhantomJS "prototypejs" : false, // Prototype and Scriptaculous "qunit" : false, // QUnit "rhino" : false, // Rhino diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..e1d4131b --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node app.js diff --git a/README.md b/README.md index 29dc834e..f3eb403a 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,50 @@ -# Todo Challenge +# ToDo List +## Features: -* Deadline: submit completed pull request by 9am on Monday -* You may use whatever level of JavaScript you feel comfortable with - pure JS, jQuery, Angular, or whatever weird and wonderful framework you want to try. Extra points for DogeScript +Users can add tasks to the do list. -Steps -------- +Users can mark a task as complete and the task will change color and strikethrough. -1. Fill out your learning plan self review for the week: https://github.com/makersacademy/learning_plan -2. Fork this repo, and clone to your local machine -3. Complete the following challenge: +Users can delete individual tasks, all completed tasks or all tasks regardless of whether they're completed or not. -## Challenge +Users can edit individual tasks and the task list will update and remain the order. -![Todo mockup](https://makersacademy.mybalsamiq.com/mockups/2914603.png?key=afabb09aef2901a2732515ae4349c1ec0458294b) +Users have a number of total tasks at the top of the page so they don't have to count them all. -Build a Todo list as a mini front-end application. You don't have to use a database, the front-end is more important - you can use an appropriate data structure stored somewhere in your JavaScript (this time only!) +Users can see a total number of incomplete tasks and a total for complete tasks. -Here are the core user stories: -``` -As a forgetful person -I want to store my tasks -So that I don't forget them +![alt text](./images/todo_screenshot.png "Todo screenshot1") -As a person with limited time -I want to instantly be able to update my todo list (adding and changing entries) -So that I have more time to think about other things +## Installation: -As a person who actually gets stuff done -I want to mark my tasks as done -So that I don't do them twice -``` +You can try the ToDo List online: -Here are some other user stories you may choose to implement: +jonathantodolist.herokuapp.com -``` -As a person with a lot of tasks -I want to be able to filter my tasks by "All", "Active", "Complete" -So that I only see the relevant tasks +or install it locally: -As a person who doesn't like counting by hand -I want to see a total number of tasks -So that I don't have to count +From the command line... -As someone who has done lots of stuff -I want to be able to clear my completed tasks -So I never see them again ``` +$ git clone git@github.com:jelgar/todo-challenge.git +$ cd todo-challenge +$ npm install +$ open index.html -As you may imagine, implementing a To-do list is very much a solved problem. However, we are mainly interested in seeing how you approach testing and design. We are looking for: - -* well written, well structured acceptance and unit tests -* clear and expressive JavaScript -* good HTML5 markup - -Don't worry about deployment, and make sure you read the CONTRIBUTING.md when submitting a pull request. - -## Extensions +``` -* Deploy the app -* Create a persistance layer (e.g. MongoDB), or use LocalStorage or the filesystem through Node -* Make it look purdy (CSS) - try a framework like Bootstrap or Foundation +## Technologies: -## CI +* AngularJS +* JQuery +* html +* CSS +* Jasmine +* Protractor +* Karma -Read the `.travis.yml` if any of the steps below don't make sense! -* Make sure you have set up `npm test` in your `package.json` so that it runs your Karma tests -* Make sure you have your Protractor config file at `e2e/conf.js` -* Make sure `npm start` spins up whatever serves up your app - `http-server`, Sinatra or Node +## Contributors: -Good luck! +Jonathan Gardiner diff --git a/app.js b/app.js new file mode 100644 index 00000000..01aebc73 --- /dev/null +++ b/app.js @@ -0,0 +1,16 @@ +var express = require('express'); +// var logger = require('morgan'); +var app = express(); + +app.set('port', (process.env.PORT || 8080)); + +// app.use(logger('dev')); +app.use(express.static(__dirname)); + +app.get('/', function(req, res) { + res.render('index.html'); +}); + +app.listen(app.get('port'), function() { + console.log('Node app is running on port', app.get('port')); +}); diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..d661c3df --- /dev/null +++ b/bower.json @@ -0,0 +1,28 @@ +{ + "name": "todo_challenge", + "homepage": "https://github.com/jelgar1/todo_challenge", + "authors": [ + "Jelgar " + ], + "description": "", + "main": "", + "moduleType": [], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "jquery": "~2.2.0", + "bootstrap": "~3.3.6", + "angular": "~1.4.9", + "angular-resource": "~1.4.9" + }, + "devDependencies": { + "angular-mocks": "~1.4.9", + "angular-route": "~1.4.9" + } +} diff --git a/common_issues.md b/common_issues.md deleted file mode 100644 index 2dcbc6da..00000000 --- a/common_issues.md +++ /dev/null @@ -1,14 +0,0 @@ -#Common issues - ToDo List - - * Tests with no expectation - * Tests with multiple expectation - * No README.md - * Incorrect installation instructions in the README - * If using a framework, delete any unnecessary code - * Avoid ```
``` - * No integration tests, or poor test coverage - * Over-reliance on online tutorials - * Not using controllerAs syntax - * not using preferred array syntax for dependency injection (to avoid minify issues) - * make use of available angular directives - e.g. checkboxes - * make use of filters where appropriate diff --git a/css/style.css b/css/style.css new file mode 100644 index 00000000..acf2d431 --- /dev/null +++ b/css/style.css @@ -0,0 +1,62 @@ +body { + color: #fdf6ee; + font-family: futura; + text-align: center; + background: url("../images/home_background.jpg") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +.main-box { + background-color: #2f3f4f; + margin:0 auto; + width: 60%; + text-align: center; + opacity: 0.9; + padding: 0.5%; +} + +.completed-true { + text-decoration: line-through; + color: #778899; +} + +.btn { + font-family: futura; + background-color: #2f3f4f; + border-color: #fdf6ee; + color: #fdf6ee; + padding: 1%; + margin: 1%; +} + +.text-box { + text-align: center; + font-family: futura; +} + +#count { + font-family; marker felt; + color: red; + font-size: 1.4em; +} + +#incomplete { + font-family; marker felt; + color: red; + font-size: 1.4em; +} + +#todo-text { + width: 50%; + text-align: center; + font-family: futura; +} + +#checkbox-form { + margin:0 auto; + width:40%; + text-align: left; +} diff --git a/images/home_background.jpg b/images/home_background.jpg new file mode 100644 index 00000000..e2fe28d2 Binary files /dev/null and b/images/home_background.jpg differ diff --git a/images/todo_screenshot.png b/images/todo_screenshot.png new file mode 100644 index 00000000..7a54a440 Binary files /dev/null and b/images/todo_screenshot.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..479ed193 --- /dev/null +++ b/index.html @@ -0,0 +1,49 @@ + + + + + Jon's ToDo List + + + + + + + + + + +
+

ToDo List by Jonathan Gardiner

+

You currently have {{ctrl.taskCount()}} + + + of which {{ctrl.incompleteCount()}} + + + incomplete.

+

What do you want to do?

+
+ + +
+
+
+ + {{task.taskname}} + + + + +

+
+
+ + +
+ + diff --git a/js/app.js b/js/app.js new file mode 100644 index 00000000..0607b385 --- /dev/null +++ b/js/app.js @@ -0,0 +1 @@ +var toDoList = angular.module('ToDoList', ['ngResource']); diff --git a/js/toDoListController.js b/js/toDoListController.js new file mode 100644 index 00000000..6279445a --- /dev/null +++ b/js/toDoListController.js @@ -0,0 +1,57 @@ +toDoList.controller('ToDoListController', [function(){ + var self = this; + + this.taskList = []; + + this.addTask = function(taskname){ + this.taskList.push({taskname:taskname, completed:false}); + this.taskname = null; + }; + + this.taskCompleted = function(index){ + this.taskList[index].completed = !this.taskList[index].completed; + }; + + this.taskCount = function(){ + return this.taskList.length; + }; + + this.incompleteCount = function(){ + var incomplete = 0 + angular.forEach(this.taskList, function(task) { + if (!task.completed) incomplete = incomplete + 1 + }); + return incomplete + }; + + this.deleteTask = function(index){ + this.taskList.splice(index,1) + }; + + this.deleteAllTasks = function(){ + this.taskList = []; + }; + + this.updateTask = function(index, taskname){ + this.taskList.splice(index, 1, {taskname:taskname, completed:false}); + }; + + this.clearCompletedTasks = function() { + var oldTaskList = this.taskList; + this.taskList = []; + angular.forEach(oldTaskList, function(task) { + if (!task.completed) self.taskList.push(task); + }); + }; + + this.toggleEditMode = function(index){ + this.taskList[index].completed ='editing' + } + this.isEditing = function(index){ + if(this.taskList[index].completed==='editing') { + return true; + } else { + return false; + } + }; +}]); diff --git a/package.json b/package.json new file mode 100644 index 00000000..1b10d5d7 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "todo_challenge", + "version": "1.0.0", + "description": "* Deadline: submit completed pull request by 9am on Monday * You may use whatever level of JavaScript you feel comfortable with - pure JS, jQuery, Angular, or whatever weird and wonderful framework you want to try. Extra points for DogeScript", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node app.js", + "postinstall": "bower install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jelgar1/todo_challenge.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/jelgar1/todo_challenge/issues" + }, + "homepage": "https://github.com/jelgar1/todo_challenge#readme", + "devDependencies": { + "jasmine-core": "^2.4.1", + "karma": "^0.13.19", + "karma-chrome-launcher": "^0.2.2", + "karma-jasmine": "^0.3.6", + "karma-phantomjs-launcher": "^1.0.0", + "karma-spec-reporter": "0.0.23", + "phantomjs": "^2.1.3", + "protractor": "^3.0.0" + }, + "dependencies": { + "bower": "^1.7.7", + "express": "^4.13.4", + "http-server": "^0.8.5" + } +} diff --git a/test/e2e/conf.js b/test/e2e/conf.js new file mode 100644 index 00000000..56ec462a --- /dev/null +++ b/test/e2e/conf.js @@ -0,0 +1,4 @@ +exports.config = { + + specs: ['toDoFeatures.js'] +} diff --git a/test/e2e/toDoFeatures.js b/test/e2e/toDoFeatures.js new file mode 100644 index 00000000..040799ee --- /dev/null +++ b/test/e2e/toDoFeatures.js @@ -0,0 +1,99 @@ +describe('ToDo List', function() { + + beforeEach(function() { + browser.get('http://localhost:8080'); + }); + var newTaskBox = element(by.model('ctrl.taskname')); + var addTaskBtn = element(by.id('todo-submit')) + var clearAllBtn = element(by.id('clear-all')) + var editBtn = element(by.id('edit')) + var editTextBox = element(by.id('edit-text')) + var submitChangesButton = element(by.id('submit-changes')) + var taskCompletedBtn = element(by.className('checkbox')); + var toDoList = element + + + it("won't let a user submit an empty task", function(){ + addTaskBtn.click(); + expect(element(by.className('completed-false')).isPresent()). + toBeFalsy(); + }); + + describe('Adds a task to the list', function() { + beforeEach(function() { + newTaskBox.sendKeys('Feed the cat'); + addTaskBtn.click(); + }); + it('can add tasks to the list', function() { + expect(element(by.className('completed-false')).getText()). + toEqual('Feed the cat'); + }); + + describe('Marks task as complete', function() { + beforeEach(function() { + taskCompletedBtn.click(); + }); + it('allows users to mark tasks as completed', function() { + expect(element(by.className('completed-true')).isPresent()).toBe(true); + }); + + it('allows users to mark tasks as incomplete again', function() { + taskCompletedBtn.click(); + expect(element(by.className('completed-false')).isPresent()).toBe(true); + }); + }); + + describe('Deletes a task', function() { + var deleteTaskBtn = element(by.id('cross')) + beforeEach(function() { + deleteTaskBtn.click(); + }); + it('allows users to delete a task', function() { + expect(element(by.className('completed-false')).isPresent()). + toBeFalsy(); + }); + }); + + it('counts the number of tasks on the list', function() { + expect(element(by.binding('ctrl.taskCount()')).getText()). + toEqual('1'); + }); + + it('allows users to edit a task', function(){ + editBtn.click(); + editTextBox.sendKeys(' EDITED'); + submitChangesButton.click(); + expect(element(by.className('completed-false')).getText()). + toEqual('Feed the cat EDITED'); + }); + + describe('Adds a second task', function() { + var clearCompletedBtn = element(by.id('clear-completed')) + var firstCheckBox = element.all(by.className('checkbox')).first(); + + beforeEach(function() { + newTaskBox.sendKeys('Clean the kitchen'); + addTaskBtn.click(); + }); + + it('allows users to clear all tasks', function() { + clearAllBtn.click(); + expect(element(by.className('completed-false')).isPresent()).toBeFalsy(); + }); + + it('allows users to clear all completed tasks', function(){ + firstCheckBox.click(); + clearCompletedBtn.click(); + expect(element(by.binding('ctrl.taskCount()')).getText()). + toEqual('1'); + }); + it('counts all tasks', function() { + expect(element(by.id('count')).getText()).toBe('2'); + }); + it('counts only incomplete tasks', function() { + firstCheckBox.click(); + expect(element(by.id('incomplete')).getText()).toBe('1'); + }); + }); + }); +}); diff --git a/test/karma.conf.js b/test/karma.conf.js new file mode 100644 index 00000000..5c9a5cb7 --- /dev/null +++ b/test/karma.conf.js @@ -0,0 +1,74 @@ +// Karma configuration +// Generated on Fri Jan 29 2016 16:00:12 GMT+0000 (GMT) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '../', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-route/angular-route.js', + 'bower_components/angular-resource/angular-resource.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'js/**/*.js', + 'test/**/*.spec.js' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['spec'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/test/todoListController.spec.js b/test/todoListController.spec.js new file mode 100644 index 00000000..d30c9925 --- /dev/null +++ b/test/todoListController.spec.js @@ -0,0 +1,70 @@ +describe('ToDoListController', function() { + beforeEach(module('ToDoList')); + + var ctrl; + + beforeEach(inject(function($controller) { + ctrl = $controller('ToDoListController'); + })); + + it('initializes with an empty list', function(){ + expect(ctrl.taskList).toEqual([]); + }); + + describe('A task is added', function() { + beforeEach(inject(function() { + ctrl.addTask('Feed the cat'); + })) + + it('can add a task to the list', function() { + expect(ctrl.taskList[0].taskname).toContain('Feed the cat'); + }); + + it('sets a task as not completed by default', function() { + expect(ctrl.taskList[0].completed).toEqual(false); + }); + + it('counts the number of total tasks', function() { + expect(ctrl.taskCount()).toEqual(1); + }); + + it('edits a task', function() { + ctrl.updateTask(0, 'Feed the cat EDITED'); + expect(ctrl.taskList[0].taskname).toEqual('Feed the cat EDITED'); + }); + + it('deletes a task', function(){ + ctrl.deleteTask(0); + expect(ctrl.taskList[0]).not.toBeDefined(); + }); + + describe('A task is marked complete', function() { + beforeEach(inject(function() { + ctrl.taskCompleted(0); + })) + it('can set a task as completed', function() { + expect(ctrl.taskList[0].completed).toEqual(true); + }); + it('counts the number of incomplete tasks', function() { + ctrl.addTask('Walk the rabbit'); + expect(ctrl.incompleteCount()).toEqual(1); + }); + }); + + describe('A second task is added', function() { + beforeEach(inject(function() { + ctrl.addTask('Learn AngularJS'); + })) + + it('clears all tasks', function(){ + ctrl.deleteAllTasks(); + expect(ctrl.taskList[0]).not.toBeDefined(); + }); + it('clears only completed tasks', function(){ + ctrl.taskCompleted(0); + ctrl.clearCompletedTasks(); + expect(ctrl.taskCount()).toEqual(1); + }); + }); + }); +});