From 16583095dd61778f73edc9130e26838bc41afd10 Mon Sep 17 00:00:00 2001 From: Carlos Lalimarmo Date: Wed, 10 Dec 2014 11:13:13 -0500 Subject: [PATCH] Add infrastructure to allow for testing, and a build step The build step allows us to have external dependencies, and selectively compile them into the distribution source for consumption by other software. Add unit tests, and inject http (jquery ajax method provider) dependency Since we inject jquery (or some substitute) in the constructor, we no longer depend on "real" jquery. Thus, we remove it from our bower deps, and other configuration. --- .bowerrc | 3 + .gitignore | 3 + Gruntfile.js | 147 +++++++++++++++++++++++++++++++++++++++ README.md | 30 +++++--- bower.json | 5 +- dist/json-gcs.js | 69 +----------------- karma.conf.js | 86 +++++++++++++++++++++++ lib/json-gcs.js | 66 ++++++++++++++++++ package.json | 31 +++++++++ spec/example_spec.coffee | 69 ++++++++++++++++++ spec/main.js | 23 ++++++ 11 files changed, 450 insertions(+), 82 deletions(-) create mode 100644 .bowerrc create mode 100644 .gitignore create mode 100644 Gruntfile.js create mode 100644 karma.conf.js create mode 100644 lib/json-gcs.js create mode 100644 package.json create mode 100644 spec/example_spec.coffee create mode 100644 spec/main.js diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..8ed7fd0 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ +"directory": "build/bower_components" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d2a37f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +coverage diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..63ad755 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,147 @@ +/*global module:false*/ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + // Metadata. + pkg: grunt.file.readJSON('package.json'), + + jshint: { + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + undef: true, + unused: true, + boss: true, + eqnull: true, + browser: true, + expr: true, + trailing: true, + }, + gruntfile: { + src: 'Gruntfile.js', + options: { + globals: { + require: false, + }, + }, + }, + lib: { + src: ['lib/**/*.js'], + options: { + globals: { + requirejs: false, + define: false, + window: false, + }, + }, + }, + spec: { + src: ['spec/**/*.js'], + options: { + globals: { + requirejs: false, + describe: false, + it: false, + xit: false, + context: false, + before: false, + beforeEach: false, + after: false, + afterEach: false, + define: false, + expect: false, + }, + }, + }, + }, + + karma: { + options: { + configFile: 'karma.conf.js' + }, + unit: { + coverageReporter: { + type: 'text-summary' + } + }, + coverage: { + coverageReporter: { + type: 'html' + } + }, + }, + + watch: { + gruntfile: { + files: '<%= jshint.gruntfile.src %>', + tasks: ['jshint:gruntfile'] + }, + dev: { + files: ['lib/**/*.js', 'spec/**/*.js', 'lib/**/*.html'], + tasks: ['build', 'karma:unit', 'jshint'] + }, + copy: { + files: [ + 'app/lib', + ], + tasks: ['copy:dev'], + }, + bower: { + files: ['bower.json'], + tasks: ['build:full', 'karma:unit', 'jshint'], + }, + }, + + copy: { + dev: { + files: [ + {expand: true, cwd: 'lib', src: ['**/*.js'], dest: 'build'}, + {expand: true, cwd: 'lib', src: ['**/*.json'], dest: 'build'}, + ], + }, + dist: { + files: [ + ], + }, + }, + + requirejs: { + compile: { + options: { + baseUrl: 'build', + name: 'json-gcs', + mainConfigFile: 'build/json-gcs.js', + out: 'dist/json-gcs.js', + optimize: 'uglify2', + }, + }, + }, + + bower: { + install: { + } + }, + + clean: ['.tmp', 'build', 'coverage'], + }); + + // These plugins provide necessary tasks. + // load all grunt tasks matching the `grunt-*` pattern + require('load-grunt-tasks')(grunt); + + // Build tasks. + grunt.registerTask('build', ['copy:dev']); + grunt.registerTask('build:full', ['clean', 'build', 'bower:install']); + grunt.registerTask('build:dist', ['clean', 'copy:dev', 'bower:install', 'requirejs']); + + // Default task. + grunt.registerTask('default', ['build:full', 'karma:unit', 'jshint']); + grunt.registerTask('coverage', 'Detailed test coverage information', ['build:full', 'karma:coverage']); + +}; diff --git a/README.md b/README.md index fc0f7ad..a65faf1 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,14 @@ Google OAuth2 token, which grants the necessary permissions to the named bucket. JsonGCS = require('json-gcs'); + $ = require('jquery'); var cloudStore = JsonGCS({ authenticator: { token: function() { return userOAuth2Token; }, - } - bucketName: "myBucket" + }, + bucketName: "myBucket", + http: $ }); cloudStore.put(objectName, jsonDocument); @@ -26,15 +28,23 @@ bucket. //do something with retrieved json document }); -## Dependencies - -The only dependency at the moment is jQuery, whose `ajax` method is used to -send request to the Google Cloud Storage JSON API. - -At some point this dependency might be removed. Until then, the module -expects to be able to `require('jquery')`. - ## Notes The module is provided as an AMD module, with simplified CommonJS wrapping. It's compatible with RequireJS, I haven't tried it in other contexts. + +So that there isn't a package dependency on jquery, we pass it into the +initialization function. We only use the `ajax` method of jquery, so if +you don't already use jquery, and don't want to introduce it to your project +you can provide your own http adapter, as long as the `ajax` method matches +jquery's signature and behavior. (This also simplifies testing). + +## Development + +To run the tests: + + grunt + +To build the dist package: + + grunt build:dist diff --git a/bower.json b/bower.json index 10a909c..6e9edbf 100644 --- a/bower.json +++ b/bower.json @@ -1,15 +1,12 @@ { "name": "json-gcs", - "version": "0.0.0", "homepage": "https://github.com/clalimarmo/json-gcs", "authors": [ "Carlos Lalimarmo " ], "description": "Store JSON documents to Google Cloud Storage", "main": "dist/json-gcs.js", - "dependencies": { - "jquery": "~2.1.1" - }, + "dependencies": {}, "moduleType": [ "amd" ], diff --git a/dist/json-gcs.js b/dist/json-gcs.js index 18de260..1b58bf0 100644 --- a/dist/json-gcs.js +++ b/dist/json-gcs.js @@ -1,68 +1 @@ -define(function(require) { - var $ = require('jquery'); - - //constructor dependency injection helper - var ensure = function(dependencyNames, dependencies) { - var onlyDependencies = {}; - for (var i = 0; i < dependencyNames.length; i++) { - var expectedDependency = dependencyNames[i]; - var injectedDependency = dependencies[expectedDependency]; - if (injectedDependency === undefined || injectedDependency === null) { - throw new Error('missing dependency:' + expectedDependency); - } else { - onlyDependencies[expectedDependency] = injectedDependency; - } - } - return onlyDependencies; - }; - - var JsonGCS = function(deps) { - deps = ensure(['authenticator', 'bucketName'], deps); - deps.authenticator = ensure(['token'], deps.authenticator); - - //helpers - var authHeader = function() { - return 'Bearer ' + deps.authenticator.token(); - }; - - var objectCreateURL = function(name) { - return 'https://www.googleapis.com/upload/storage/v1/b/' + - deps.bucketName + '/o?uploadType=media&name=' + name; - }; - - var objectGetURL = function(name) { - return 'https://storage.googleapis.com/' + deps.bucketName + '/' + name; - }; - - //instance methods - var instance = {}; - - instance.put = function(name, doc) { - $.ajax( - objectCreateURL(name), - { - method: 'POST', - headers: { 'Authorization': authHeader() }, - data: JSON.stringify(doc) - } - ); - }; - - instance.get = function(name, handleJson) { - $.ajax( - objectGetURL(name), - { - headers: { 'Authorization': authHeader() }, - success: function(doc) { - var json = JSON.parse(doc); - handleJson(json); - } - } - ); - }; - - return instance; - }; - - return JsonGCS; -}); +define("json-gcs",[],function(){var t=function(t,e){for(var n={},o=0;o + JsonGCS = require('json-gcs') + + describe 'JsonGCS', -> + mocks = {} + instance = null + + beforeEach -> + mocks.http = {} + mocks.authenticator = { + token: -> 'stub' + } + mocks.http = {} + + instance = JsonGCS( + http: mocks.http + authenticator: mocks.authenticator + bucketName: 'my-bucket' + ) + + context 'get', -> + + it 'makes an ajax http get request', -> + requestURL = 'request url' + requestOptions = 'request options' + requestResponse = 'response' + + mocks.authenticator.token = -> '1234-oauth-token' + + mocks.response = '{"very-important":"data"}' + + mocks.http.ajax = (url, opts) -> + requestURL = url + requestOptions = opts + opts.success(mocks.response) + + mocks.httpSuccessHandler = (json) -> + requestResponse = json + + instance.get('my-object-name', mocks.httpSuccessHandler) + + expect(requestURL).to.eq('https://storage.googleapis.com/my-bucket/my-object-name') + expect(requestOptions.headers['Authorization']).to.eq('Bearer 1234-oauth-token') + expect(requestResponse).to.deep.eq(JSON.parse(mocks.response)) + + context 'put', -> + + it 'makes an ajax http post request with the data to be saved', -> + requestURL = 'request url' + requestOptions = 'request options' + requestResponse = 'response' + + mocks.authenticator.token = -> '4321-oauth-token' + + mocks.userData = { + name: 'Carlos' + points: 12 + } + + mocks.http.ajax = (url, opts) -> + requestURL = url + requestOptions = opts + + instance.put('my-profile', mocks.userData) + + expect(requestURL).to.eq('https://www.googleapis.com/upload/storage/v1/b/my-bucket/o?uploadType=media&name=my-profile') + expect(requestOptions.method).to.eq('POST') + expect(requestOptions.headers['Authorization']).to.eq('Bearer 4321-oauth-token') + expect(requestOptions.data).to.eq(JSON.stringify(mocks.userData)) diff --git a/spec/main.js b/spec/main.js new file mode 100644 index 0000000..db504c9 --- /dev/null +++ b/spec/main.js @@ -0,0 +1,23 @@ +// based on https://github.com/kjbekkelund/karma-requirejs +var tests = []; +for (var file in window.__karma__.files) { + if (window.__karma__.files.hasOwnProperty(file)) { + if (/spec\.js$/.test(file)) { + tests.push(file); + } + if (/spec\.coffee$/.test(file)) { + tests.push(file); + } + } +} + +requirejs.config({ + // Karma serves files from '/base' + baseUrl: '/base/build', + paths: {}, + shim: {}, + deps: tests, + + // start test run, only after Require.js is done + callback: window.__karma__.start, +});