├── .gitignore ├── README.md ├── js ├── app.js └── pomodoro.js ├── templates └── timer.html ├── bower.json ├── index.html ├── LICENSE ├── karma.conf.js └── tests ├── pomodoroControllerSpec.js └── pomodoroServiceSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pomodoro 2 | An angular js pomodoro 3 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | angular.module('foc-app', ['foc-pomodoro']); -------------------------------------------------------------------------------- /templates/timer.html: -------------------------------------------------------------------------------- 1 | : 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomodoro", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/drFabio/pomodoro", 5 | "authors": [ 6 | "fabio " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "angular": "~1.3.15" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | Test 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fabio Oliveira Costa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue May 19 2015 20:38:49 GMT-0300 (BRT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | files: [ 17 | './bower_components/angular/angular.min.js', 18 | './node_modules/angular-mocks/angular-mocks.js', 19 | './js/*.js', 20 | './tests/*Spec.js' 21 | // './tests/pomodoroServiceSpec.js' 22 | // './tests/pomodoroControllerSpec.js' 23 | 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [ 29 | ], 30 | 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | }, 36 | 37 | 38 | // test results reporter to use 39 | // possible values: 'dots', 'progress' 40 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 41 | reporters: ['progress'], 42 | 43 | 44 | // web server port 45 | port: 9876, 46 | 47 | 48 | // enable / disable colors in the output (reporters and logs) 49 | colors: true, 50 | 51 | 52 | // level of logging 53 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 54 | logLevel: config.LOG_INFO, 55 | 56 | 57 | // enable / disable watching file and executing tests whenever any file changes 58 | autoWatch: false, 59 | 60 | 61 | // start these browsers 62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 63 | browsers: ['PhantomJS'], 64 | 65 | 66 | // Continuous Integration mode 67 | // if true, Karma captures browsers, runs the tests and exits 68 | singleRun: true 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /tests/pomodoroControllerSpec.js: -------------------------------------------------------------------------------- 1 | describe('PomodoroCtrl', function(){ 2 | var scope; //we'll use this scope in our tests 3 | var ctrl; 4 | var theInterval; 5 | var rootScope; 6 | var mockPomodoroService={ 7 | startPomodoro:function(){ 8 | rootScope.$broadcast('focNewPomodoroStarted','task',25); 9 | }, 10 | getStatus:function(){ 11 | return 'task'; 12 | }, 13 | getStatusDuration:function(){ 14 | return 25; 15 | }, 16 | getEllapsedSeconds:function(){ 17 | return 0; 18 | }, 19 | getEllapsedMinutes:function(){ 20 | return 0; 21 | }, 22 | 23 | }; 24 | // mock Application to allow us to inject our own dependencies 25 | beforeEach(angular.mock.module('foc-pomodoro')); 26 | //mock the controller for the same reason and include $rootScope and $controller 27 | beforeEach(angular.mock.inject(function($rootScope, $controller,$interval){ 28 | theInterval=$interval; 29 | //create an empty scope 30 | scope = $rootScope.$new(); 31 | rootScope=$rootScope; 32 | //declare the controller and inject our empty scope 33 | ctrl=$controller('PomodoroCtrl', {$scope: scope,$interval:$interval,focPomodoroService:mockPomodoroService}); 34 | })); 35 | 36 | // tests start here 37 | it('Starst a timer on count', function(){ 38 | spyOn(mockPomodoroService, 'startPomodoro').and.callThrough(); 39 | ctrl.start(); 40 | expect(mockPomodoroService.startPomodoro).toHaveBeenCalled(); 41 | theInterval.flush(1200); 42 | 43 | expect(ctrl.minutes).toEqual(0); 44 | expect(ctrl.seconds).toEqual(1); 45 | }); 46 | 47 | it('Stops counting when the minute limit is reached',function(){ 48 | ctrl.start(); 49 | var duration=mockPomodoroService.getStatusDuration(); 50 | theInterval.flush(duration*1000*60+200); 51 | expect(ctrl.seconds).toEqual(0); 52 | expect(ctrl.minutes).toEqual(duration); 53 | }); 54 | 55 | it('Starts counting when a new event is triggered ',function(){ 56 | spyOn(ctrl,'startCount'); 57 | rootScope.$broadcast('focNewPomodoroStarted','task',25); 58 | expect(ctrl.startCount).toHaveBeenCalled(); 59 | }); 60 | // it('Stops the count when it\'s cancelled',function(){ 61 | 62 | // }); 63 | 64 | 65 | }); -------------------------------------------------------------------------------- /tests/pomodoroServiceSpec.js: -------------------------------------------------------------------------------- 1 | describe('Pomodoro Service',function(){ 2 | var pomodoroService; 3 | var scope; 4 | var timeout; 5 | describe('Default config',function(){ 6 | beforeEach(function(){ 7 | angular.mock.module('foc-pomodoro'); 8 | angular.mock.inject(function($rootScope,$timeout,$injector,focPomodoroService){ 9 | scope = $rootScope; 10 | timeout=$timeout; 11 | pomodoroService=focPomodoroService; 12 | }); 13 | }); 14 | it('Starts as a task state ',function(){ 15 | expect(pomodoroService.getStatus()).toEqual('task'); 16 | }); 17 | it('Have the default task duration of 25 minutes ',function(){ 18 | pomodoroService._setStatus('task'); 19 | expect(pomodoroService.getStatusDuration()).toEqual(25); 20 | }); 21 | 22 | it('Have the default long break duration of 15 minutes ',function(){ 23 | pomodoroService._setStatus('long_break'); 24 | expect(pomodoroService.getStatusDuration()).toEqual(15); 25 | }); 26 | it('Have the default short break duration of 5 minutes ',function(){ 27 | pomodoroService._setStatus('short_break'); 28 | expect(pomodoroService.getStatusDuration()).toEqual(5); 29 | }); 30 | it('Follows the cycle as Task,Short,Short,Short,Break',function(){ 31 | var expectedResults=['task','short_break','task','short_break','task','short_break','task','long_break']; 32 | var expectedStatus; 33 | var actualStatus; 34 | for(var j=0;j<2;j++){ 35 | for(var i=0;i=60){ 33 | self.seconds=0; 34 | self.minutes++; 35 | } 36 | if(self.minutes>=self.statusDuration){ 37 | self.stopCount(); 38 | } 39 | }, 1000); 40 | }; 41 | this.stopCount = function() { 42 | if (angular.isDefined(stop)) { 43 | $interval.cancel(stop); 44 | stop = undefined; 45 | } 46 | }; 47 | $scope.$on("$destroy", function() { 48 | self.stopCount(); 49 | unbindList.map(function(u){ 50 | u(); 51 | }) 52 | }); 53 | } 54 | function PomodoroService($rootScope,$timeout){ 55 | this.currentStatus=TASK_STATUS; 56 | this.durations={}; 57 | this.durations[TASK_STATUS]=25; 58 | this.durations[SHORT_BREAK_STATUS]=5; 59 | this.durations[LONG_BREAK_STAUS]=15; 60 | this.pomodoroCount=0; 61 | this.behaviour=AUTO_BEHAVIOUR; 62 | this._scope=$rootScope; 63 | this._timeout=$timeout; 64 | this._timer; 65 | this._timePomodoroStarted; 66 | } 67 | PomodoroService.prototype.getEllapsedSeconds = function() { 68 | var ellapsedSeconds=Math.round((new Date().getTime()-this._timePomodoroStarted)/1000); 69 | return ellapsedSeconds%60; 70 | }; 71 | PomodoroService.prototype.getEllapsedMinutes = function() { 72 | var ellapsedSeconds=Math.round((new Date().getTime()-this._timePomodoroStarted)/1000); 73 | return Math.round(ellapsedSeconds/60); 74 | }; 75 | PomodoroService.prototype.cancelPomodoro = function() { 76 | this.cancelTimer(); 77 | var ellapsedSeconds=(new Date().getTime()-this._timePomodoroStarted)/1000; 78 | this._scope.$broadcast(POMODORO_CANCELLED_EVENT,this.getStatus(),this.getStatusDuration(),ellapsedSeconds); 79 | }; 80 | PomodoroService.prototype.startPomodoro = function() { 81 | this.startCounting(); 82 | this._scope.$broadcast(NEW_POMODORO_EVENT,this.getStatus(),this.getStatusDuration()); 83 | }; 84 | PomodoroService.prototype.startCounting = function() { 85 | this.cancelTimer(); 86 | this._timePomodoroStarted=new Date().getTime(); 87 | var self=this; 88 | this._timer=this._timeout(function(){ 89 | self._timerFinished(); 90 | },this.getStatusDuration()*60*1000); 91 | }; 92 | PomodoroService.prototype._startNewEvent = function() { 93 | this.nextStatus(); 94 | this.startPomodoro(); 95 | }; 96 | PomodoroService.prototype._timerFinished = function() { 97 | this._scope.$broadcast(POMODORO_FINISHED_EVENT,this.getStatus(),this.getStatusDuration()); 98 | if(this.behaviour==AUTO_BEHAVIOUR){ 99 | this._startNewEvent(); 100 | } 101 | }; 102 | PomodoroService.prototype.cancelTimer = function() { 103 | if (angular.isDefined(this._timer)) { 104 | this._timeout.cancel(this._timer); 105 | this._timer = undefined; 106 | } 107 | }; 108 | PomodoroService.prototype.getStatus=function(){ 109 | return this.currentStatus; 110 | }; 111 | PomodoroService.prototype._setStatus=function(status){ 112 | this.currentStatus=status; 113 | }; 114 | PomodoroService.prototype.getStatusDuration=function(){ 115 | return this.durations[this.getStatus()]; 116 | }; 117 | PomodoroService.prototype.nextStatus=function(){ 118 | switch(this.getStatus()) { 119 | case TASK_STATUS: 120 | this.pomodoroCount++; 121 | if(this.pomodoroCount>=4){ 122 | this.pomodoroCount=0; 123 | this._setStatus(LONG_BREAK_STAUS); 124 | } 125 | else{ 126 | this._setStatus(SHORT_BREAK_STATUS); 127 | } 128 | break; 129 | case LONG_BREAK_STAUS: 130 | case SHORT_BREAK_STATUS: 131 | this._setStatus(TASK_STATUS); 132 | break; 133 | } 134 | return this.getStatus(); 135 | }; 136 | PomodoroService.prototype.setDurations = function(durations) { 137 | var allowedDurations=[TASK_STATUS,SHORT_BREAK_STATUS,LONG_BREAK_STAUS]; 138 | var self=this; 139 | allowedDurations.forEach(function(a){ 140 | self.durations[a]=durations[a]; 141 | }); 142 | }; 143 | angular.module('foc-pomodoro', []) 144 | .service('focPomodoroService',['$rootScope','$timeout',PomodoroService]) 145 | .controller('TasksCtrl', function($scope) {}) 146 | .directive('pomodoroTimer', [function() { 147 | return { 148 | templateUrl:'/templates/timer.html', 149 | restrict: 'E', 150 | controllerAs:'pomodoroCtrl', 151 | controller:['$scope','$interval','focPomodoroService',pomodoroController] 152 | }; 153 | }] 154 | ); --------------------------------------------------------------------------------