From b9b46eee6de17b331a9de78179a56fbd0b52c57e Mon Sep 17 00:00:00 2001 From: Carlos Mantilla Date: Tue, 21 Jul 2015 00:03:14 +0300 Subject: [PATCH] feat(weather): improve the cache mechanism, test(all): define basic workflow for test. --- .travis.yml | 18 ++++++++ bower.json | 9 +++- e2e-tests/protractor.conf.js | 19 ++++++++ e2e-tests/scenarios.js | 7 +++ gulpfile.js | 21 +++++++++ karma.conf.js | 32 ++++++++++++++ package.json | 25 ++++++++++- src/angular-weather.js | 85 ++++++++++++++++++++++++------------ test/angular-weather.spec.js | 10 +++++ 9 files changed, 195 insertions(+), 31 deletions(-) create mode 100644 .travis.yml create mode 100644 e2e-tests/protractor.conf.js create mode 100644 e2e-tests/scenarios.js create mode 100644 karma.conf.js create mode 100644 test/angular-weather.spec.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..32a5797 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +# See http://docs.travis-ci.com/user/languages/javascript-with-nodejs/ +# npm install && npm start are run by travis, everything is covered in the package.json file + +language: node_js +node_js: + - 0.10 + - 0.12 + +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - npm start > /dev/null & + - npm run update-webdriver + - sleep 1 # give server time to start + +script: + - node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox + - node_modules/.bin/protractor e2e-tests/protractor.conf.js --browser=firefox \ No newline at end of file diff --git a/bower.json b/bower.json index b048849..9eee6a6 100644 --- a/bower.json +++ b/bower.json @@ -23,5 +23,12 @@ "bower_components", "test", "tests" - ] + ], + "dependencies": { + "localforage": "~1.2.4" + }, + "devDependencies": { + "angular": "~1.4.3", + "angular-mocks": "~1.4.3" + } } diff --git a/e2e-tests/protractor.conf.js b/e2e-tests/protractor.conf.js new file mode 100644 index 0000000..def491f --- /dev/null +++ b/e2e-tests/protractor.conf.js @@ -0,0 +1,19 @@ +exports.config = { + allScriptsTimeout: 11000, + + specs: [ + '*.js' + ], + + capabilities: { + 'browserName': 'chrome' + }, + + baseUrl: 'http://localhost:8000/app/', + + framework: 'jasmine', + + jasmineNodeOpts: { + defaultTimeoutInterval: 30000 + } +}; \ No newline at end of file diff --git a/e2e-tests/scenarios.js b/e2e-tests/scenarios.js new file mode 100644 index 0000000..8b8fb29 --- /dev/null +++ b/e2e-tests/scenarios.js @@ -0,0 +1,7 @@ +'use strict'; + +/* https://github.com/angular/protractor/blob/master/docs/toc.md */ + +describe('angular-weather', function() { + +}); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 45c3a75..609722f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,4 +1,6 @@ var gulp = require('gulp'); +var karma = require('karma').server; +var karmaConfig = require('./karma.conf'); var clean = require('gulp-clean'); var uglify = require('gulp-uglify'); var ngAnnotate = require('gulp-ng-annotate'); @@ -15,4 +17,23 @@ gulp.task('build', ['clean:dist'], function() { .pipe(gulp.dest('dist')); }); +gulp.task('test', function () { + + karmaConfig({ + set: function (testConfig) { + + extend(testConfig, { + singleRun: ciMode, + autoWatch: !ciMode, + browsers: ['PhantomJS'] + }); + + karma.start(testConfig, function (exitCode) { + plugins.util.log('Karma has exited with ' + exitCode); + process.exit(exitCode); + }); + } + }); +}); + gulp.task('default', ['build']); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..750610a --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,32 @@ +module.exports = function (config) { + config.set({ + + basePath: './', + + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'test/**/*.spec.js' + ], + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome', 'Firefox', 'PhantomJs'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine', + 'karma-junit-reporter', + 'karma-phantomjs-launcher' + ], + + junitReporter: { + outputFile: 'test_out/unit.xml', + suite: 'unit' + } + + }); +}; \ No newline at end of file diff --git a/package.json b/package.json index 247b47b..4375d21 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,34 @@ "main": "gulpfile.js", "dependencies": {}, "devDependencies": { + "bower": "^1.4.1", "gulp": "^3.9.0", "gulp-clean": "^0.3.1", "gulp-ng-annotate": "^1.0.0", - "gulp-uglify": "^1.2.0" + "gulp-uglify": "^1.2.0", + "http-server": "^0.8.0", + "jasmine-core": "^2.3.4", + "karma": "^0.13.2", + "karma-chrome-launcher": "^0.2.0", + "karma-firefox-launcher": "^0.1.6", + "karma-jasmine": "^0.3.6", + "karma-junit-reporter": "^0.3.1", + "protractor": "^2.1.0", + "shelljs": "^0.5.1" }, "scripts": { - "test": "gulp test" + "postinstall": "bower install", + "prestart": "npm install", + "start": "http-server -a localhost -p 8000 -c-1", + "pretest": "npm install", + "test": "karma start karma.conf.js", + "test-single-run": "karma start karma.conf.js --single-run", + "preupdate-webdriver": "npm install", + "update-webdriver": "webdriver-manager update", + "preprotractor": "npm run update-webdriver", + "protractor": "protractor e2e-tests/protractor.conf.js", + "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + sed(/sourceMappingURL=angular-loader.min.js.map/,'sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map','app/bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\"", + "test-gulp": "gulp test" }, "repository": { "type": "git", diff --git a/src/angular-weather.js b/src/angular-weather.js index 4718246..546f0bb 100644 --- a/src/angular-weather.js +++ b/src/angular-weather.js @@ -1,5 +1,7 @@ 'use strict'; +var extend = angular.extend; + angular.module('angular-weather', []) .constant('openweatherEndpoint', 'http://api.openweathermap.org/data/2.5/weather') .constant('Config', { @@ -28,28 +30,39 @@ angular.module('angular-weather', []) } }; }) - .service('weather', function ($q, $http, $timeout, $rootScope, openweatherEndpoint, weatherIcons, Config) { - var self = this; - - // A private cache key. - var cache = {}; + .service('weather', function ($q, $http, $interval, openweatherEndpoint, weatherIcons, Config) { + var weather = this; + // interval id, keep it to handle the auto refresh + var interval; // Promise in progress of Weather. var getWeather; + // Service options + var options = { + refresh: true, + delay: 3600000 // An hour. + }; - // Update event broadcast name. - var broadcastUpdateEventName = 'weatherChanged'; + // Public service API + weather.get = get; /** * Return the promise with the category list, from cache or the server. * * @param city - string - * The city name. Ex: Houston + * The city name. Ex: Houston + * @param _options - object + * The options to handle. + * refresh: activate to get new weather information in interval + * of delay time. + * delay: interval of time in miliseconds. * * @returns {Promise} */ - this.get = function(city) { - getWeather = $q.when(getWeather || angular.copy(cache.data) || getWeatherFromServer(city)); + function get(city, _options) { + extend(options, _options, {city: city}); + + getWeather = $q.when(getWeather || angular.copy(getCache()) || getWeatherFromServer(city)); // Clear the promise cached, after resolve or reject the promise. Permit access to the cache data, when // the promise excecution is done (finally). @@ -58,7 +71,7 @@ angular.module('angular-weather', []) }); return getWeather; - }; + } /** * Return Weather array from the server. @@ -83,6 +96,10 @@ angular.module('angular-weather', []) // Prepare the Weather object. var weatherData = prepareWeather(data); setCache(weatherData); + + // Start refresh automatic the weather, according the interval of time. + options.refresh && startRefreshWeather(); + deferred.resolve(weatherData); }); @@ -90,22 +107,39 @@ angular.module('angular-weather', []) } /** - * Save Weather in cache, and broadcast en event to inform that the Weather data changed. + * Save Weather in cache, and emit en event to inform that the Weather data changed. * * @param data * Collection resulted from the request. */ function setCache(data) { - // Cache Weather data. - cache = { - data: data, - timestamp: new Date() - }; - // Clear cache in 10 minute. - $timeout(function() { - cache.data = undefined; - }, 600000); - $rootScope.$broadcast(broadcastUpdateEventName); + // Save cache Weather data directly to localStorage. + localforage.setItem('aw.cache', data); + localforage.setItem('aw.updatedAt', new Date()); + } + + /** + * Return a promise with the weather data cached. + */ + function getCache() { + return localforage('aw.cache'); + } + + /** + * Start an interval to refresh the weather cache data with new server data. + */ + function startRefreshWeather() { + interval = $interval(getWeatherFromServer(options.city), options.delay); + localforage.setItem('aw.refreshing', true); + } + + /** + * Stop interval to refresh the weather cache data. + */ + function stopRefreshWeather() { + $interval(interval); + interval = undefined; + localforage.setItem('aw.refreshing', false); } /** @@ -113,12 +147,11 @@ angular.module('angular-weather', []) * * Return the Weather object into a promises. * - * @param getWeather - {$q.promise) + * @param weatherData - {$q.promise) * Promise of list of Weather, comming from cache or the server. * */ function prepareWeather(weatherData) { - return { temperature: weatherData.main.temp, icon: (angular.isDefined(weatherIcons[weatherData.weather[0].icon])) ? weatherData.weather[0].icon : weatherData.weather[0].id, @@ -126,10 +159,6 @@ angular.module('angular-weather', []) } } - $rootScope.$on('clearCache', function() { - cache = {}; - }); - }) .factory('weatherIcons', function() { // * All the codes by are based in the openweather API diff --git a/test/angular-weather.spec.js b/test/angular-weather.spec.js new file mode 100644 index 0000000..18ad5a6 --- /dev/null +++ b/test/angular-weather.spec.js @@ -0,0 +1,10 @@ +'use strict'; + +describe('angular-weather', function() { + describe('dummy test', function() { + + it('should ', function() { + expect(true).toEqual(true); + }); + }); +}); \ No newline at end of file