Skip to content

Commit

Permalink
feat(weather): improve the cache mechanism, test(all): define basic w…
Browse files Browse the repository at this point in the history
…orkflow for test.
  • Loading branch information
Carlos Mantilla committed Jul 20, 2015
1 parent 47f2293 commit b9b46ee
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 31 deletions.
18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 8 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,12 @@
"bower_components",
"test",
"tests"
]
],
"dependencies": {
"localforage": "~1.2.4"
},
"devDependencies": {
"angular": "~1.4.3",
"angular-mocks": "~1.4.3"
}
}
19 changes: 19 additions & 0 deletions e2e-tests/protractor.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
exports.config = {
allScriptsTimeout: 11000,

specs: [
'*.js'
],

capabilities: {
'browserName': 'chrome'
},

baseUrl: 'http://localhost:8000/app/',

framework: 'jasmine',

jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};
7 changes: 7 additions & 0 deletions e2e-tests/scenarios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

/* https://github.com/angular/protractor/blob/master/docs/toc.md */

describe('angular-weather', function() {

});
21 changes: 21 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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']);
32 changes: 32 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -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'
}

});
};
25 changes: 23 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
85 changes: 57 additions & 28 deletions src/angular-weather.js
Original file line number Diff line number Diff line change
@@ -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', {
Expand Down Expand Up @@ -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).
Expand All @@ -58,7 +71,7 @@ angular.module('angular-weather', [])
});

return getWeather;
};
}

/**
* Return Weather array from the server.
Expand All @@ -83,53 +96,69 @@ 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);
});

return deferred.promise;
}

/**
* 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);
}

/**
* Prepare Weather object with order by list, tree and collection indexed by id.
*
* 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,
description: weatherData.weather[0].description
}
}

$rootScope.$on('clearCache', function() {
cache = {};
});

})
.factory('weatherIcons', function() {
// * All the codes by are based in the openweather API
Expand Down
10 changes: 10 additions & 0 deletions test/angular-weather.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

describe('angular-weather', function() {
describe('dummy test', function() {

it('should ', function() {
expect(true).toEqual(true);
});
});
});

0 comments on commit b9b46ee

Please sign in to comment.