From b44665e75d7a351d50dd75e6ea3bb49b4e901fd1 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Fri, 6 Jan 2017 21:27:54 -0800 Subject: [PATCH 1/6] installed and configured karma test runner --- .dockerignore | 1 + .gitignore | 1 + karma.conf.js | 69 +++++++++++++++++++ package.json | 40 ++++++++--- .../test/services/angular-view-array.spec.js | 13 ++++ static/test/shims/window.shim.js | 30 ++++++++ 6 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 karma.conf.js create mode 100644 static/test/services/angular-view-array.spec.js create mode 100644 static/test/shims/window.shim.js diff --git a/.dockerignore b/.dockerignore index cd4ba187b..5a266e8e1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,5 +16,6 @@ run-local.sh .tox htmlcov .coverage +coverage .cache test/__pycache__ diff --git a/.gitignore b/.gitignore index e2aa3f78b..d5d39119f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ GIT_HEAD .python-version .pylintrc .coverage +coverage htmlcov .tox .cache diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 000000000..be0b206da --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,69 @@ +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine'], + files: [ + // CDN resources + 'node_modules/jquery/dist/jquery.js', + 'node_modules/angular/angular.js', + 'node_modules/angular-animate/angular-animate.js', + 'node_modules/angular-cookies/angular-cookies.js', + 'node_modules/angular-md5/angular-md5.js', + 'node_modules/angular-mocks/angular-mocks.js', + 'node_modules/angular-route/angular-route.js', + 'node_modules/angular-sanitize/angular-sanitize.js', + 'node_modules/moment/moment.js', + 'node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.js', + 'node_modules/eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js', + 'node_modules/bootbox/bootbox.js', + 'node_modules/underscore/underscore.js', + 'node_modules/restangular/dist/restangular.js', + 'node_modules/d3/d3.js', + 'node_modules/raven-js/dist/raven.js', + 'node_modules/cal-heatmap/cal-heatmap.js', + + // static/lib resources + 'static/lib/**/*.js', + + // FIXME: Upgrade angular-file-upload -> ng-file-upload + 'https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/1.4.0/angular-file-upload.js', + 'https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/1.4.0/angular-file-upload-html5-shim.js', + + // Application resources + 'static/js/tour.js', + 'static/js/core-ui.js', + 'static/js/core-config-setup.js', + 'static/js/app.js', + 'static/js/**/*.js', + + // Tests + 'static/test/**/*.js', + ], + exclude: [ + 'static/js/build/bundle.js', + ], + preprocessors: { + 'static/lib/ngReact/react.ngReact.min.js': ['webpack'], + 'static/lib/angular-moment.min.js': ['webpack'], + }, + webpack: {}, + webpackMiddleware: { + stats: 'errors-only' + }, + reporters: ['dots', 'coverage'], + coverageReporter: { + dir: 'coverage', + type: 'html' + }, + client: { + captureConsole: true + }, + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['PhantomJS', 'Chrome'], + singleRun: false, + concurrency: Infinity + }); +}; diff --git a/package.json b/package.json index 24906dac4..d9a23d7eb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "version": "1.0.0", "scripts": { + "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "build": "./node_modules/.bin/webpack --progress -p -v", "watch": "./node_modules/.bin/webpack --watch" }, @@ -16,20 +17,39 @@ "@types/angular": "1.5.16", "@types/react": "0.14.39", "@types/react-dom": "0.14.17", - "angular": "1.5.8", - "moment": "2.16.0", - "ngreact": "0.3.0", - "react": "15.3.2", - "react-dom": "15.3.2", - "typescript": "2.0.3" + "angular": "1.5.3", + "angular-animate": "^1.5.3", + "angular-cookies": "^1.5.3", + "angular-route": "^1.5.3", + "angular-sanitize": "^1.5.3", + "bootbox": "^4.1.0", + "bootstrap": "^3.3.2", + "bootstrap-datepicker": "^1.6.4", + "cal-heatmap": "^3.3.10", + "d3": "^3.3.3", + "eonasdan-bootstrap-datetimepicker": "^4.17.43", + "jquery": "1.12.4", + "raven-js": "^3.1.0", + "react": "^15.3.2", + "react-dom": "^15.3.2", + "restangular": "^1.2.0", + "underscore": "^1.5.2" }, "devDependencies": { - "css-loader": "0.25.0", + "angular-mocks": "^1.5.3", + "jasmine-core": "^2.5.2", + "karma": "^0.13.22", + "karma-chrome-launcher": "^2.0.0", + "karma-coverage": "^0.5.5", + "karma-es6-shim": "^1.0.0", + "karma-intl-shim": "^1.0.3", + "karma-jasmine": "^0.3.8", + "karma-phantomjs-launcher": "^1.0.0", + "karma-webpack": "^1.8.1", "node-sass": "3.10.1", - "sass-loader": "4.0.2", - "source-map-loader": "0.1.5", - "style-loader": "0.13.1", + "phantomjs-prebuilt": "^2.1.7", "ts-loader": "0.9.5", + "typescript": "2.0.3", "typings": "1.4.0", "webpack": "1.13.3" } diff --git a/static/test/services/angular-view-array.spec.js b/static/test/services/angular-view-array.spec.js new file mode 100644 index 000000000..88d8d3f3b --- /dev/null +++ b/static/test/services/angular-view-array.spec.js @@ -0,0 +1,13 @@ +describe("Service: AngularViewArray", function() { + var angularViewArray; + + beforeEach(module('quay')); + + beforeEach(inject(function($injector) { + angularViewArray = $injector.get('AngularViewArray'); + })); + + it("sanity test", function() { + expect(angularViewArray.create).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/static/test/shims/window.shim.js b/static/test/shims/window.shim.js new file mode 100644 index 000000000..3f59c01c3 --- /dev/null +++ b/static/test/shims/window.shim.js @@ -0,0 +1,30 @@ +// Add properties to window object used by 'app.js' to avoid test errors + +window.__config = { + 'SERVER_HOSTNAME': "", + 'PREFERRED_URL_SCHEME': "", +}; +window.__features = {}; +window.__oauth = { + 'GITHUB_TRIGGER_CONFIG': { + 'CLIENT_ID': "", + 'GITHUB_ENDPOINT': "", + 'AUTHORIZE_ENDPOINT': "", + }, + 'GITLAB_TRIGGER_CONFIG': { + 'CLIENT_ID': "", + 'GITLAB_ENDPOINT': "", + 'AUTHORIZE_ENDPOINT': "", + } +}; +window.__endpoints = { + "/api/v1/user/": { + "get": { + "operationId": "getLoggedInUser", + "parameters": [] + }, + "x-name": "endpoints.api.user.User", + "x-path": "/api/v1/user/", + "x-tag": "user" + }, +}; From 659417f7efa4b2f716571c792601be34db911f01 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Sat, 7 Jan 2017 00:28:02 -0800 Subject: [PATCH 2/6] tests for AngularViewArray service --- endpoints/common.py | 2 +- karma.conf.js | 4 - package.json | 7 +- static/js/services/angular-view-array.js | 91 ----------- .../angular-view-array/angular-view-array.js | 104 +++++++++++++ .../angular-view-array.spec.js | 144 ++++++++++++++++++ .../test/services/angular-view-array.spec.js | 13 -- 7 files changed, 254 insertions(+), 111 deletions(-) delete mode 100644 static/js/services/angular-view-array.js create mode 100644 static/js/services/angular-view-array/angular-view-array.js create mode 100644 static/js/services/angular-view-array/angular-view-array.spec.js delete mode 100644 static/test/services/angular-view-array.spec.js diff --git a/endpoints/common.py b/endpoints/common.py index 35adca062..621895938 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -136,7 +136,7 @@ def random_string(): def list_files(path, extension): import os def matches(f): - return os.path.splitext(f)[1] == '.' + extension + return os.path.splitext(f)[1] == '.' + extension and f.split(os.path.extsep)[1] != 'spec' def join_path(dp, f): # Remove the static/ prefix. It is added in the template. diff --git a/karma.conf.js b/karma.conf.js index be0b206da..a63de3ca4 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -25,10 +25,6 @@ module.exports = function (config) { // static/lib resources 'static/lib/**/*.js', - // FIXME: Upgrade angular-file-upload -> ng-file-upload - 'https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/1.4.0/angular-file-upload.js', - 'https://cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/1.4.0/angular-file-upload-html5-shim.js', - // Application resources 'static/js/tour.js', 'static/js/core-ui.js', diff --git a/package.json b/package.json index d9a23d7eb..6a6d594ce 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "version": "1.0.0", "scripts": { - "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", + "test": "./node_modules/.bin/karma start --browsers PhantomJS", "build": "./node_modules/.bin/webpack --progress -p -v", "watch": "./node_modules/.bin/webpack --watch" }, @@ -42,11 +42,14 @@ "karma-chrome-launcher": "^2.0.0", "karma-coverage": "^0.5.5", "karma-es6-shim": "^1.0.0", - "karma-intl-shim": "^1.0.3", "karma-jasmine": "^0.3.8", "karma-phantomjs-launcher": "^1.0.0", "karma-webpack": "^1.8.1", + "css-loader": "0.25.0", "node-sass": "3.10.1", + "sass-loader": "4.0.2", + "source-map-loader": "0.1.5", + "style-loader": "0.13.1", "phantomjs-prebuilt": "^2.1.7", "ts-loader": "0.9.5", "typescript": "2.0.3", diff --git a/static/js/services/angular-view-array.js b/static/js/services/angular-view-array.js deleted file mode 100644 index 9ccd0df20..000000000 --- a/static/js/services/angular-view-array.js +++ /dev/null @@ -1,91 +0,0 @@ - /** - * Specialized wrapper around array which provides a toggle() method for viewing the contents of the - * array in a manner that is asynchronously filled in over a short time period. This prevents long - * pauses in the UI for ngRepeat's when the array is significant in size. - */ -angular.module('quay').factory('AngularViewArray', ['$interval', function($interval) { - var ADDTIONAL_COUNT = 20; - - function _ViewArray() { - this.isVisible = false; - this.visibleEntries = null; - this.hasEntries = false; - this.entries = []; - this.hasHiddenEntries = false; - - this.timerRef_ = null; - this.currentIndex_ = 0; - } - - _ViewArray.prototype.length = function() { - return this.entries.length; - }; - - _ViewArray.prototype.get = function(index) { - return this.entries[index]; - }; - - _ViewArray.prototype.push = function(elem) { - this.entries.push(elem); - this.hasEntries = true; - - if (this.isVisible) { - this.startTimer_(); - } - }; - - _ViewArray.prototype.toggle = function() { - this.setVisible(!this.isVisible); - }; - - _ViewArray.prototype.setVisible = function(newState) { - this.isVisible = newState; - - this.visibleEntries = []; - this.currentIndex_ = 0; - - if (newState) { - this.showAdditionalEntries_(); - this.startTimer_(); - } else { - this.stopTimer_(); - } - }; - - _ViewArray.prototype.showAdditionalEntries_ = function() { - var i = 0; - for (i = this.currentIndex_; i < (this.currentIndex_ + ADDTIONAL_COUNT) && i < this.entries.length; ++i) { - this.visibleEntries.push(this.entries[i]); - } - - this.currentIndex_ = i; - this.hasHiddenEntries = this.currentIndex_ < this.entries.length; - if (this.currentIndex_ >= this.entries.length) { - this.stopTimer_(); - } - }; - - _ViewArray.prototype.startTimer_ = function() { - if (this.timerRef_) { return; } - - var that = this; - this.timerRef_ = $interval(function() { - that.showAdditionalEntries_(); - }, 10); - }; - - _ViewArray.prototype.stopTimer_ = function() { - if (this.timerRef_) { - $interval.cancel(this.timerRef_); - this.timerRef_ = null; - } - }; - - var service = { - 'create': function() { - return new _ViewArray(); - } - }; - - return service; -}]); \ No newline at end of file diff --git a/static/js/services/angular-view-array/angular-view-array.js b/static/js/services/angular-view-array/angular-view-array.js new file mode 100644 index 000000000..d23ae3949 --- /dev/null +++ b/static/js/services/angular-view-array/angular-view-array.js @@ -0,0 +1,104 @@ +(function() { + 'use strict'; + + /** + * Specialized wrapper around array which provides a toggle() method for viewing the contents of the + * array in a manner that is asynchronously filled in over a short time period. This prevents long + * pauses in the UI for ngRepeat's when the array is significant in size. + */ + angular + .module('quay') + .factory('AngularViewArray', factory); + + factory.$inject = [ + '$interval' + ]; + + function factory($interval) { + var ADDTIONAL_COUNT = 20; + + function _ViewArray() { + this.isVisible = false; + this.visibleEntries = null; + this.hasEntries = false; + this.entries = []; + this.hasHiddenEntries = false; + + this.timerRef_ = null; + this.currentIndex_ = 0; + } + + _ViewArray.prototype.length = function() { + return this.entries.length; + }; + + _ViewArray.prototype.get = function(index) { + return this.entries[index]; + }; + + _ViewArray.prototype.push = function(elem) { + this.entries.push(elem); + this.hasEntries = true; + + if (this.isVisible) { + this.startTimer_(); + } + }; + + _ViewArray.prototype.toggle = function() { + this.setVisible(!this.isVisible); + }; + + _ViewArray.prototype.setVisible = function(newState) { + this.isVisible = newState; + + this.visibleEntries = []; + this.currentIndex_ = 0; + + if (newState) { + this.showAdditionalEntries_(); + this.startTimer_(); + } else { + this.stopTimer_(); + } + }; + + _ViewArray.prototype.showAdditionalEntries_ = function() { + var i = 0; + for (i = this.currentIndex_; i < (this.currentIndex_ + ADDTIONAL_COUNT) && i < this.entries.length; ++i) { + this.visibleEntries.push(this.entries[i]); + } + + this.currentIndex_ = i; + this.hasHiddenEntries = this.currentIndex_ < this.entries.length; + if (this.currentIndex_ >= this.entries.length) { + this.stopTimer_(); + } + }; + + _ViewArray.prototype.startTimer_ = function() { + if (this.timerRef_) { return; } + + var that = this; + this.timerRef_ = $interval(function() { + that.showAdditionalEntries_(); + }, 10); + }; + + _ViewArray.prototype.stopTimer_ = function() { + if (this.timerRef_) { + $interval.cancel(this.timerRef_); + this.timerRef_ = null; + } + }; + + var service = { + 'create': function() { + return new _ViewArray(); + } + }; + + return service; + } + +})(); \ No newline at end of file diff --git a/static/js/services/angular-view-array/angular-view-array.spec.js b/static/js/services/angular-view-array/angular-view-array.spec.js new file mode 100644 index 000000000..314a3b5a1 --- /dev/null +++ b/static/js/services/angular-view-array/angular-view-array.spec.js @@ -0,0 +1,144 @@ +describe("Service: AngularViewArray", function() { + var angularViewArray; + + beforeEach(module('quay')); + + beforeEach(inject(function($injector) { + angularViewArray = $injector.get('AngularViewArray'); + })); + + describe("create", function() { + + it("returns a ViewArray object", function() { + var viewArray = angularViewArray.create(); + + expect(viewArray).toBeDefined(); + }); + + describe("returned ViewArray object", function() { + var viewArray; + + beforeEach(function() { + viewArray = angularViewArray.create(); + }); + + describe("constructor", function() { + // TODO + }); + + describe("length", function() { + + it("returns the number of entries", function() { + viewArray.entries = [{}, {}, {}]; + + expect(viewArray.length()).toEqual(viewArray.entries.length); + }); + }); + + describe("get", function() { + + it("returns the entry at a given index", function() { + var index = 8; + viewArray.entries = new Array(10); + viewArray.entries[index] = 3; + + expect(viewArray.get(index)).toEqual(viewArray.entries[index]); + }); + }); + + describe("push", function() { + + it("adds given element to the end of entries", function() { + var element = 3; + var originalLength = viewArray.length(); + viewArray.push(element); + + expect(viewArray.entries.length).toEqual(originalLength + 1); + expect(viewArray.get(originalLength)).toEqual(element); + }); + + it("sets 'hasEntries' to true", function() { + viewArray.push(2); + + expect(viewArray.hasEntries).toBe(true); + }); + + it("starts timer if 'isVisible' is true", function() { + spyOn(viewArray, "startTimer_").and.returnValue(); + viewArray.isVisible = true; + viewArray.push(2); + + expect(viewArray.startTimer_).toHaveBeenCalled(); + }); + + it("does not start timer if 'isVisible' is false", function() { + spyOn(viewArray, "startTimer_").and.returnValue(); + viewArray.isVisible = false; + viewArray.push(2); + + expect(viewArray.startTimer_).not.toHaveBeenCalled(); + }); + }); + + describe("toggle", function() { + + it("sets 'isVisible' to false if currently true", function() { + viewArray.isVisible = true; + viewArray.toggle(); + + expect(viewArray.isVisible).toBe(false); + }); + + it("sets 'isVisible' to true if currently false", function() { + viewArray.isVisible = false; + viewArray.toggle(); + + expect(viewArray.isVisible).toBe(true); + }); + }); + + describe("setVisible", function() { + + it("sets 'isVisible' to false if given false", function() { + viewArray.setVisible(false); + + expect(viewArray.isVisible).toBe(false); + }); + + it("sets 'visibleEntries' to empty array if given false", function() { + viewArray.setVisible(false); + + expect(viewArray.visibleEntries.length).toEqual(0); + }); + + it("shows additional entries if given true", function() { + spyOn(viewArray, "showAdditionalEntries_").and.returnValue(); + viewArray.setVisible(true); + + expect(viewArray.showAdditionalEntries_).toHaveBeenCalled(); + }); + + it("does not show additional entries if given false", function() { + spyOn(viewArray, "showAdditionalEntries_").and.returnValue(); + viewArray.setVisible(false); + + expect(viewArray.showAdditionalEntries_).not.toHaveBeenCalled(); + }); + + it("starts timer if given true", function() { + spyOn(viewArray, "startTimer_").and.returnValue(); + viewArray.setVisible(true); + + expect(viewArray.startTimer_).toHaveBeenCalled(); + }); + + it("stops timer if given false", function() { + spyOn(viewArray, "stopTimer_").and.returnValue(); + viewArray.setVisible(true); + + expect(viewArray.stopTimer_).toHaveBeenCalled(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/static/test/services/angular-view-array.spec.js b/static/test/services/angular-view-array.spec.js deleted file mode 100644 index 88d8d3f3b..000000000 --- a/static/test/services/angular-view-array.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -describe("Service: AngularViewArray", function() { - var angularViewArray; - - beforeEach(module('quay')); - - beforeEach(inject(function($injector) { - angularViewArray = $injector.get('AngularViewArray'); - })); - - it("sanity test", function() { - expect(angularViewArray.create).toBeDefined(); - }); -}); \ No newline at end of file From 9248e4e8aaf708beb32646457fb360c651e365d2 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Sat, 7 Jan 2017 04:24:26 -0800 Subject: [PATCH 3/6] tests and refactoring for AngularRouteBuilder --- .dockerignore | 1 + .gitignore | 1 + karma.conf.js | 1 - static/js/angular-route-builder.js | 38 ------ static/js/app.js | 5 +- static/js/route-builder/route-builder.js | 57 +++++++++ static/js/route-builder/route-builder.spec.js | 120 ++++++++++++++++++ 7 files changed, 182 insertions(+), 41 deletions(-) delete mode 100644 static/js/angular-route-builder.js create mode 100644 static/js/route-builder/route-builder.js create mode 100644 static/js/route-builder/route-builder.spec.js diff --git a/.dockerignore b/.dockerignore index 5a266e8e1..0a5878cef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,4 +18,5 @@ htmlcov .coverage coverage .cache +.npm-debug.log test/__pycache__ diff --git a/.gitignore b/.gitignore index d5d39119f..1a5d2c3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ coverage htmlcov .tox .cache +.npm-debug.log diff --git a/karma.conf.js b/karma.conf.js index a63de3ca4..51836ffec 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -8,7 +8,6 @@ module.exports = function (config) { 'node_modules/angular/angular.js', 'node_modules/angular-animate/angular-animate.js', 'node_modules/angular-cookies/angular-cookies.js', - 'node_modules/angular-md5/angular-md5.js', 'node_modules/angular-mocks/angular-mocks.js', 'node_modules/angular-route/angular-route.js', 'node_modules/angular-sanitize/angular-sanitize.js', diff --git a/static/js/angular-route-builder.js b/static/js/angular-route-builder.js deleted file mode 100644 index d38ff7eca..000000000 --- a/static/js/angular-route-builder.js +++ /dev/null @@ -1,38 +0,0 @@ -var AngularRouteBuilder = function(routeProvider, pages, profiles, currentProfile) { - this.routeProvider = routeProvider; - this.pages = pages; - this.profiles = profiles; - - for (var i = 0; i < profiles.length; ++i) { - var current = profiles[i]; - if (current.id == currentProfile) { - this.profiles = this.profiles.slice(i); - break; - } - } -}; - -AngularRouteBuilder.prototype.otherwise = function(options) { - this.routeProvider.otherwise(options); -}; - -AngularRouteBuilder.prototype.route = function(path, pagename) { - // Lookup the page, matching our lists of profiles. - var pair = this.pages.get(pagename, this.profiles); - if (!pair) { - throw Error('Unknown page: ' + pagename); - } - - // Create the route. - var foundProfile = pair[0]; - var page = pair[1]; - var templateUrl = foundProfile.templatePath + page.templateName; - - var options = jQuery.extend({}, page.flags || {}); - options['templateUrl'] = templateUrl; - options['reloadOnSearch'] = false; - options['controller'] = page.controller; - - this.routeProvider.when(path, options); - return this; -}; \ No newline at end of file diff --git a/static/js/app.js b/static/js/app.js index ba49e6181..58170c42e 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -90,7 +90,8 @@ quayApp.config(['$compileProvider', function ($compileProvider) { }]); // Configure the routes. -quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeProvider, $locationProvider, pages) { +quayApp.config(['$routeProvider', '$locationProvider', 'pages', 'RouteBuilderProvider', + function($routeProvider, $locationProvider, pages, RouteBuilderProvider) { $locationProvider.html5Mode(true); // WARNING WARNING WARNING @@ -101,7 +102,7 @@ quayApp.config(['$routeProvider', '$locationProvider', 'pages', function($routeP var layoutProfile = 'layout'; window.console.log('Using layout profile: ' + layoutProfile); - var routeBuilder = new AngularRouteBuilder($routeProvider, pages, [ + var routeBuilder = new RouteBuilderProvider.$get()($routeProvider, pages, [ // Start with the old pages (if we asked for it). {id: 'old-layout', templatePath: '/static/partials/'}, diff --git a/static/js/route-builder/route-builder.js b/static/js/route-builder/route-builder.js new file mode 100644 index 000000000..8a94706eb --- /dev/null +++ b/static/js/route-builder/route-builder.js @@ -0,0 +1,57 @@ +(function() { + 'use strict'; + + angular + .module("quay") + .factory('RouteBuilder', factory); + + factory.$inject = [ + + ]; + + function factory() { + function RouteBuilder(routeProvider, pages, profiles, currentProfile) { + this.routeProvider = routeProvider; + this.pages = pages; + this.profiles = profiles; + + for (var i = 0; i < profiles.length; ++i) { + if (profiles[i].id == currentProfile) { + this.profiles = this.profiles.slice(i); + break; + } + } + } + + RouteBuilder.prototype.otherwise = function(options) { + this.routeProvider.otherwise(options); + }; + + RouteBuilder.prototype.route = function(path, pagename) { + // Lookup the page, matching our lists of profiles. + var pair = this.pages.get(pagename, this.profiles); + if (!pair) { + throw Error('Unknown page: ' + pagename); + } + + // Create the route. + var foundProfile = pair[0]; + var page = pair[1]; + var templateUrl = foundProfile.templatePath + page.templateName; + + var options = jQuery.extend({}, page.flags || {}); + options['templateUrl'] = templateUrl; + options['reloadOnSearch'] = false; + options['controller'] = page.controller; + + this.routeProvider.when(path, options); + + return this; + }; + + return function(routeProvider, pages, profiles, currentProfile) { + return new RouteBuilder(routeProvider, pages, profiles, currentProfile); + } + } + +})(); \ No newline at end of file diff --git a/static/js/route-builder/route-builder.spec.js b/static/js/route-builder/route-builder.spec.js new file mode 100644 index 000000000..fa883cb02 --- /dev/null +++ b/static/js/route-builder/route-builder.spec.js @@ -0,0 +1,120 @@ +describe("Service: RouteBuilder", function() { + var RouteBuilder; + var routeProviderMock; + var pagesMock; + var profiles; + var currentProfile; + + beforeEach(module('quay')); + + beforeEach(inject(function($injector) { + profiles = [ + {id: 'old-layout', templatePath: '/static/partials/'}, + {id: 'layout', templatePath: '/static/partials/'} + ]; + currentProfile = 'layout'; + routeProviderMock = jasmine.createSpyObj('routeProvider', ['otherwise', 'when']); + pagesMock = jasmine.createSpyObj('pagesMock', ['get', 'create']); + RouteBuilder = $injector.get('RouteBuilder'); + })); + + describe("constructor", function() { + + it("returns a RouteBuilder object", function() { + var routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, currentProfile); + + expect(routeBuilder).toBeDefined(); + }); + + it("initializes dependencies", function() { + var routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, currentProfile); + + expect(routeBuilder.routeProvider).toEqual(routeProviderMock); + expect(routeBuilder.pages).toEqual(pagesMock); + expect(routeBuilder.profiles).toBeDefined(); + }); + + it("sets 'profiles' to all given profiles if given current profile does not match any of the given profiles' id", function() { + var routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, 'fake-profile'); + + expect(routeBuilder.profiles).toEqual(profiles); + }); + + it("sets 'profiles' to the first given profile with id matching given current profile", function() { + var routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, currentProfile); + + expect(routeBuilder.profiles).toEqual([profiles[1]]); + }); + }); + + describe("otherwise", function() { + var routeBuilder; + + beforeEach(function() { + routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, currentProfile); + }); + + it("calls routeProvider to set fallback route with given options", function() { + var options = {1: "option"}; + routeBuilder.otherwise(options); + + expect(routeProviderMock.otherwise.calls.argsFor(0)[0]).toEqual(options); + }); + }); + + describe("route", function() { + var routeBuilder; + var path; + var pagename; + var page; + + beforeEach(function() { + path = '/repository/:namespace/:name'; + pagename = 'repo-view'; + page = { + templateName: 'repository.html', + reloadOnSearch: false, + controller: jasmine.createSpy('pageController'), + flags: {}, + }; + routeBuilder = new RouteBuilder(routeProviderMock, pagesMock, profiles, currentProfile); + }); + + it("calls pages with given pagename and 'profiles' to get matching page and profile pair", function() { + pagesMock.get.and.returnValue([profiles[1], page]); + routeBuilder.route(path, pagename); + + expect(pagesMock.get.calls.argsFor(0)[0]).toEqual(pagename); + expect(pagesMock.get.calls.argsFor(0)[1]).toEqual(routeBuilder.profiles); + }); + + it("throws error if no matching page/profile pair found", function() { + pagesMock.get.and.returnValue(); + try { + routeBuilder.route(path, pagename); + fail(); + } catch (error) { + expect(error.message).toEqual('Unknown page: ' + pagename); + } + }); + + it("calls routeProvider to set route for given path and options", function() { + pagesMock.get.and.returnValue([profiles[1], page]); + var expectedOptions = { + templateUrl: profiles[1].templatePath + page.templateName, + reloadOnSearch: false, + controller: page.controller, + }; + routeBuilder.route(path, pagename); + + expect(routeProviderMock.when.calls.argsFor(0)[0]).toEqual(path); + expect(routeProviderMock.when.calls.argsFor(0)[1]).toEqual(expectedOptions); + }); + + it("returns itself (the RouteBuilder instance)", function() { + pagesMock.get.and.returnValue([profiles[1], page]); + + expect(routeBuilder.route(path, pagename)).toEqual(routeBuilder); + }); + }); +}); \ No newline at end of file From 72151dc16268f5efee02558a199d3fb13d1c857c Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Sat, 7 Jan 2017 15:10:52 -0800 Subject: [PATCH 4/6] simplified karma.conf.js --- karma.conf.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 51836ffec..3a917a709 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -25,10 +25,6 @@ module.exports = function (config) { 'static/lib/**/*.js', // Application resources - 'static/js/tour.js', - 'static/js/core-ui.js', - 'static/js/core-config-setup.js', - 'static/js/app.js', 'static/js/**/*.js', // Tests From a6ac3b8940cf8faf4376c7dfe9b17c45c7617b2e Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Mon, 9 Jan 2017 11:59:24 -0800 Subject: [PATCH 5/6] added npm test to container build --- Dockerfile | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5a8d54fb8..5ad0ad370 100644 --- a/Dockerfile +++ b/Dockerfile @@ -104,6 +104,9 @@ RUN cd grunt && npm install # Run grunt RUN cd grunt && grunt +# Run front-end tests +RUN npm test + # Optimize our images ADD static/img static/img RUN jpegoptim static/img/**/*.jpg diff --git a/package.json b/package.json index 6a6d594ce..36de3d949 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "version": "1.0.0", "scripts": { - "test": "./node_modules/.bin/karma start --browsers PhantomJS", + "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "build": "./node_modules/.bin/webpack --progress -p -v", "watch": "./node_modules/.bin/webpack --watch" }, From 04232f7fd461c233f0d245387a1fd45e7f28bfc8 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Mon, 9 Jan 2017 18:05:49 -0800 Subject: [PATCH 6/6] fixed Dockerfile npm test --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5ad0ad370..a8494c361 100644 --- a/Dockerfile +++ b/Dockerfile @@ -94,6 +94,10 @@ ADD static/js/directives/components static/dist/components RUN npm install -g webpack RUN webpack +# Run front-end tests +ADD karma.conf.js karma.conf.js +RUN npm test + # Install Grunt RUN npm install -g grunt-cli @@ -104,9 +108,6 @@ RUN cd grunt && npm install # Run grunt RUN cd grunt && grunt -# Run front-end tests -RUN npm test - # Optimize our images ADD static/img static/img RUN jpegoptim static/img/**/*.jpg