Make "npm test" run Karma tests (#140)

* Make "npm test" run Karma tests

Result is this:

```
$ npm test

> docker-registry-frontend@0.0.2 pretest /Users/marca/dev/git-repos/docker-registry-frontend_2
> npm install

> docker-registry-frontend@0.0.2 postinstall /Users/marca/dev/git-repos/docker-registry-frontend_2
> bower install

> docker-registry-frontend@0.0.2 test /Users/marca/dev/git-repos/docker-registry-frontend_2
> karma start test/karma.conf.js

09 07 2016 13:36:20.771:WARN [karma]: No captured browser, open http://localhost:8080/
09 07 2016 13:36:20.781:INFO [karma]: Karma v1.1.1 server started at http://localhost:8080/
09 07 2016 13:36:20.782:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
09 07 2016 13:36:20.788:INFO [launcher]: Starting browser PhantomJS
09 07 2016 13:36:21.250:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#Z4MF21dfMgThOi3zAAAA with id 9538616
PhantomJS 2.1.1 (Mac OS X 0.0.0) Controller: MainCtrl should attach a list of awesomeThings to the scope FAILED
	forEach@bower_components/angular/angular.js:326:24
	loadModules@bower_components/angular/angular.js:4115:12
	createInjector@bower_components/angular/angular.js:4041:22
	workFn@bower_components/angular-mocks/angular-mocks.js:2464:60
	loaded@http://localhost:8080/context.js:151:17
	bower_components/angular/angular.js:4155:53
	TypeError: undefined is not an object (evaluating 'scope.awesomeThings') in test/spec/controllers/main.js (line 20)
	test/spec/controllers/main.js:20:17
	loaded@http://localhost:8080/context.js:151:17
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.005 secs / 0.006 secs)
```

Fixes #139

* Make MainController tests work

This makes the `MainController` tests pass.

```
$ npm test

> docker-registry-frontend@0.0.2 pretest /Users/marca/dev/git-repos/docker-registry-frontend_2
> npm install

> docker-registry-frontend@0.0.2 postinstall /Users/marca/dev/git-repos/docker-registry-frontend_2
> bower install

> docker-registry-frontend@0.0.2 test /Users/marca/dev/git-repos/docker-registry-frontend_2
> karma start test/karma.conf.js

09 07 2016 17:06:59.974:WARN [karma]: No captured browser, open http://localhost:8080/
09 07 2016 17:06:59.986:INFO [karma]: Karma v1.1.1 server started at http://localhost:8080/
09 07 2016 17:06:59.986:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
09 07 2016 17:07:00.133:INFO [launcher]: Starting browser PhantomJS
09 07 2016 17:07:01.051:INFO [PhantomJS 2.1.1 (Mac OS X 0.0.0)]: Connected on socket /#6enEIdsugSVdKVioAAAA with id 62465837
PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 1 of 1 SUCCESS (0.004 secs / 0.024 secs)
```

* test/karma.conf.js: Add more files

E.g.:

  - angular-bootstrap,
  - angular-bootstrap-checkbox
  - angular-filter
  - angular-loading-bar
  - angular-moment
  - angular-smart-table

I also alphabetized the list to make it easier to check if something is
already included.

* test/karma.conf.js: Add 'dots' reporter

* Add app/app.spec.js

`app/app.spec.js` contains tests for various routes, which should be
very useful for finding and fixing routing bugs.

See:

  - #97
  - #100
  - #104
  - #121

* Enable junit and spec Karma reporters

* app/app.spec.js: Check scope attributes

* app/app.spec.js: Check scope.appMode

* test/spec/controllers/main.js: Check appVersion & registryHost

* MainController: use registry-services

In `MainController`, depend on the `registry-services` module instead of
the `registry-host-services` module.

* Remove app/services/registry-host-services.js

Remove `app/services/registry-host-services.js` because it seems to be
an older, unused version of `app/services/registry-services.js`

* repository-list-controller: Add dependencies

Specify that the `repository-list-controller` module depends on
`ngRoute` and `ui.bootstrap` modules.

Otherwise controller tests of this module fail.

* $scope.repositories = data;

In `repository-list-controller`, do:

    $scope.repositories = data;

in the promise callback. Otherwise, the new tests in
`app/repository/repository-list-controller.spec.js` fail.

This is because promises get resolved in templates but not in until
tests -- see
http://stackoverflow.com/questions/15048132/angularjs-promise-not-being-resolved-in-unit-test

* Add unit test for RepositoryListController

in `app/repository/repository-list-controller.spec.js`.

      RepositoryListController
        ✓ should attach some keys to the scope

* Gruntfile.js: Use DOCKER_REGISTRY_{HOST,PORT}

Use `DOCKER_REGISTRY_HOST` and `DOCKER_REGISTRY_PORT` environment
variables so that one can specify a custom Docker registry server while
developing, without hacking `Gruntfile.js`.
This commit is contained in:
Marc Abramowitz 2016-07-18 23:41:24 -07:00 committed by Konrad Kleine
parent aaf15350cf
commit ea2bcd55cd
10 changed files with 274 additions and 58 deletions

View file

@ -79,8 +79,8 @@ module.exports = function (grunt) {
proxies: [
{
context: '/v2',
host: 'path-to-your-registry-v2',
port: 5000,
host: process.env.DOCKER_REGISTRY_HOST,
port: process.env.DOCKER_REGISTRY_PORT,
https: false,
xforward: false,
headers: {

174
app/app.spec.js Normal file
View file

@ -0,0 +1,174 @@
'use strict';
describe('docker-registry-frontend', function() {
var $route, $location, $rootScope, $httpBackend, $controller;
beforeEach(module('docker-registry-frontend'));
beforeEach(inject(function(_$route_, _$location_, _$rootScope_, _$httpBackend_, _$controller_) {
$route = _$route_;
$location = _$location_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$controller = _$controller_;
}));
it('/home should display home page', function() {
$httpBackend.expectGET('home.html').respond(200);
$location.path('/home');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('home.html');
expect($route.current.controller).toBe('HomeController');
var scope = {};
var expectedAppMode = {
"browseOnly": true,
"defaultRepositoriesPerPage": 20,
"defaultTagsPerPage": 10
}
$controller('HomeController', {$scope: scope});
$httpBackend.expectGET('/app-mode.json').respond(expectedAppMode);
$httpBackend.flush();
jasmine.addCustomEqualityTester(angular.equals);
expect(scope.appMode).toEqual(expectedAppMode);
});
it('/repositories should display repository list page', function() {
$httpBackend.expectGET('repository/repository-list.html').respond(200);
$location.path('/repositories');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-list.html');
expect($route.current.controller).toBe('RepositoryListController');
var scope = {};
$controller('RepositoryListController', {$scope: scope});
expect(scope.reposPerPage).toBeUndefined();
});
it('/repositories/10 should display repository list page', function() {
$httpBackend.expectGET('repository/repository-list.html').respond(200);
$location.path('/repositories/10');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-list.html');
expect($route.current.controller).toBe('RepositoryListController');
var scope = {};
$controller('RepositoryListController', {$scope: scope});
// expect(scope.reposPerPage).toBe(10);
});
it('/repositories/20 should display repository list page', function() {
$httpBackend.expectGET('repository/repository-list.html').respond(200);
$location.path('/repositories/20');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-list.html');
expect($route.current.controller).toBe('RepositoryListController');
var scope = {};
$controller('RepositoryListController', {$scope: scope});
// expect(scope.reposPerPage).toBe(20);
});
it('URL with repositoryUser and repositoryName and no tagsPerPage should display repository detail page', function() {
$httpBackend.expectGET('repository/repository-detail.html').respond(200);
$location.path('/repository/owner/name');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-detail.html');
expect($route.current.controller).toBe('RepositoryDetailController');
var scope = {};
$controller('RepositoryDetailController', {$scope: scope});
expect(scope.repositoryUser).toBe('owner');
expect(scope.repositoryName).toBe('name');
expect(scope.repository).toBe('owner/name');
expect(scope.maxTagsPage).toBeUndefined();
});
it('URL with repositoryUser and repositoryName and tagsPerPage should display repository detail page', function() {
$httpBackend.expectGET('repository/repository-detail.html').respond(200);
$location.path('/repository/owner/name/10');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-detail.html');
expect($route.current.controller).toBe('RepositoryDetailController');
var scope = {};
$controller('RepositoryDetailController', {$scope: scope});
expect(scope.repositoryUser).toBe('owner');
expect(scope.repositoryName).toBe('name');
expect(scope.repository).toBe('owner/name');
});
// This test currently fails; this URL is incorrectly routing to the home page
// @todo @FIXME
//
// it('URL with repositoryName but no repositoryUser and no tagsPerPage should display repository detail page', function() {
// $httpBackend.expectGET('repository/repository-detail.html').respond(200);
// $location.path('/repository/cx');
// $rootScope.$digest();
// expect($route.current.templateUrl).toBe('repository/repository-detail.html');
// expect($route.current.controller).toBe('RepositoryDetailController');
// });
it('URL with repositoryName but no repositoryUser and tagsPerPage should display repository detail page', function() {
$httpBackend.expectGET('repository/repository-detail.html').respond(200);
$location.path('/repository/cx/10');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('repository/repository-detail.html');
expect($route.current.controller).toBe('RepositoryDetailController');
var scope = {};
$controller('RepositoryDetailController', {$scope: scope});
// expect(scope.repositoryUser).toBeUndefined();
// expect(scope.repositoryName).toBe('cx');
// expect(scope.repository).toBe('cx');
});
it('/about should display about page', function() {
$httpBackend.expectGET('about.html').respond(200);
$location.path('/about');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('about.html');
});
it('/tag/repositoryUser/repositoryName/latest should display tag detail page', function() {
$httpBackend.expectGET('tag/tag-detail.html').respond(200);
$location.path('/tag/repositoryUser/repositoryName/latest');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('tag/tag-detail.html');
expect($route.current.controller).toBe('TagController');
var scope = {};
$controller('TagController', {$scope: scope});
expect(scope.repositoryUser).toBe('repositoryUser');
expect(scope.repositoryName).toBe('repositoryName');
expect(scope.repository).toBe('repositoryUser/repositoryName');
expect(scope.tagName).toBe('latest');
});
// This test currently fails; this URL is incorrectly routing to the home page
// @todo @FIXME
//
// it('/tag/repositoryName/latest should display tag detail page', function() {
// $httpBackend.expectGET('tag/tag-detail.html').respond(200);
// $location.path('/tag/repositoryName/latest');
// $rootScope.$digest();
// expect($route.current.templateUrl).toBe('tag/tag-detail.html');
// expect($route.current.controller).toBe('TagController');
// });
it('/image/88e37c7099fa should display image detail page', function() {
$httpBackend.expectGET('tag/image-detail.html').respond(200);
$location.path('/image/88e37c7099fa');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('tag/image-detail.html');
expect($route.current.controller).toBe('ImageController');
});
it('/image/88e37c7099fa/tag should display create tag page', function() {
$httpBackend.expectGET('tag/create-tag.html').respond(200);
$location.path('/image/88e37c7099fa/tag');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('tag/create-tag.html');
expect($route.current.controller).toBe('CreateTagController');
});
it('/unknown-url should display home page', function() {
$httpBackend.expectGET('home.html').respond(200);
$location.path('/unknown-url');
$rootScope.$digest();
expect($route.current.templateUrl).toBe('home.html');
expect($route.current.controller).toBe('HomeController');
});
});

View file

@ -7,7 +7,7 @@
* # MainController
* Controller of the docker-registry-frontend
*/
angular.module('main-controller', [])
angular.module('main-controller', ['ngRoute', 'app-version-services', 'registry-services'])
.controller('MainController', ['$scope', '$route', '$routeParams', '$location', 'AppVersion', 'RegistryHost',
function($scope, $route, $routeParams, $location, AppVersion, RegistryHost){
this.$route = $route;
@ -15,4 +15,4 @@ angular.module('main-controller', [])
this.$routeParams = $routeParams;
$scope.appVersion = AppVersion.query();
$scope.registryHost = RegistryHost.query();
}]);
}]);

View file

@ -7,7 +7,7 @@
* # RepositoryListController
* Controller of the docker-registry-frontend
*/
angular.module('repository-list-controller', ['registry-services', 'app-mode-services'])
angular.module('repository-list-controller', ['ngRoute', 'ui.bootstrap', 'registry-services', 'app-mode-services'])
.controller('RepositoryListController', ['$scope', '$route', '$routeParams', '$location', '$modal', 'Repository', 'AppMode',
function($scope, $route, $routeParams, $location, $modal, Repository, AppMode){
@ -47,6 +47,7 @@ angular.module('repository-list-controller', ['registry-services', 'app-mode-ser
// To watch for changes on a property inside the object "repositories"
// we first have to make sure the promise is ready.
$scope.repositories.$promise.then(function(data) {
$scope.repositories = data;
$scope.$watch('repositories.repos|filter:{selected:true}', function(nv) {
$scope.selectedRepositories = nv.map(function (repo) {
return repo.name;

View file

@ -0,0 +1,49 @@
'use strict';
describe('RepositoryListController', function() {
// load the controller's module
beforeEach(module('repository-list-controller'));
var $controller, $httpBackend, $q, $rootScope;
beforeEach(inject(function(_$controller_, _$httpBackend_, _$q_, _$rootScope_) {
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
}));
it('should attach some keys to the scope', function() {
var $scope = $rootScope.$new();
var route = {
'current': {
'params': {
'lastNamespace': 'lastNamespace',
'lastRepository': 'lastRepository',
'reposPerPage': 10
}
}
};
var mockRepositoryReturnValue = {
repos: [{username: 'username', name: 'name', selected: true}],
lastNamespace: 'lastNamespace',
lastRepository: 'lastRepository'
};
var mockRepository = {query: null};
spyOn(mockRepository, 'query').and.returnValue({$promise: $q.when(mockRepositoryReturnValue)});
var expectedAppMode = {"browseOnly": true, "defaultRepositoriesPerPage": 20, "defaultTagsPerPage": 10};
$httpBackend.expectGET('/app-mode.json').respond(expectedAppMode);
var ctrl = $controller('RepositoryListController', {$scope: $scope, $route: route, Repository: mockRepository});
$httpBackend.flush();
expect($scope.reposPerPage).toBe(10);
expect($scope.lastNamespace).toEqual('lastNamespace');
expect($scope.lastRepository).toEqual('lastRepository');
expect($scope.selectedRepositories).toEqual(['name']);
expect(mockRepository.query).toHaveBeenCalled();
expect($scope.repositories).toEqual(mockRepositoryReturnValue);
});
});

View file

@ -1,11 +0,0 @@
'use strict';
angular.module('registry-host-services', ['ngResource'])
.factory('RegistryHost', ['$resource', function($resource){
return $resource('/registry-host.json', {}, {
'query': {
method:'GET',
isArray: false,
},
});
}]);

View file

@ -27,7 +27,14 @@
"grunt-svgmin": "^3.1.2",
"grunt-usemin": "^3.1.1",
"grunt-wiredep": "^2.0.0",
"jasmine": "^2.4.1",
"jasmine-core": "^2.4.1",
"jshint-stylish": "^2.1.0",
"karma": "^1.1.1",
"karma-jasmine": "^1.0.2",
"karma-junit-reporter": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.1",
"karma-spec-reporter": "0.0.26",
"load-grunt-tasks": "^3.4.0",
"time-grunt": "^1.3.0"
},
@ -35,7 +42,10 @@
"node": ">=0.10.0"
},
"scripts": {
"test": "grunt test"
"postinstall": "bower install",
"pretest": "npm install",
"test": "karma start test/karma.conf.js",
"test-single-run": "karma start test/karma.conf.js --single-run"
},
"description": "A pure web-based frontend to a docker-registry",
"main": "Gruntfile.js",

View file

@ -16,18 +16,25 @@ module.exports = function(config) {
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
reporters: ['junit', 'spec'],
// list of files / patterns to load in the browser
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-animate/angular-animate.js',
'bower_components/angular-bootstrap/ui-bootstrap.js',
'bower_components/angular-bootstrap-checkbox/angular-bootstrap-checkbox.js',
'bower_components/angular-cookies/angular-cookies.js',
'bower_components/angular-filter/dist/angular-filter.js',
'bower_components/angular-loading-bar/src/loading-bar.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-moment/angular-moment.js',
'bower_components/angular-resource/angular-resource.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/angular-smart-table/src/smart-table.module.js',
'bower_components/angular-touch/angular-touch.js',
'app/scripts/**/*.js',
'test/mock/**/*.js',
'app/**/*.js',
'test/spec/**/*.js'
],
@ -50,10 +57,10 @@ module.exports = function(config) {
],
// Which plugins to enable
plugins: [
'karma-phantomjs-launcher',
'karma-jasmine'
],
// plugins: [
// 'karma-phantomjs-launcher',
// 'karma-jasmine'
// ],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit

View file

@ -1,22 +0,0 @@
'use strict';
describe('Controller: AboutCtrl', function () {
// load the controller's module
beforeEach(module('dockerRegistryFrontendApp'));
var AboutCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
AboutCtrl = $controller('AboutCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
});
});

View file

@ -1,22 +1,30 @@
'use strict';
describe('Controller: MainCtrl', function () {
describe('MainController', function() {
// load the controller's module
beforeEach(module('dockerRegistryFrontendApp'));
beforeEach(module('main-controller'));
var MainCtrl,
scope;
var ctrl;
var scope = {};
var $httpBackend;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
beforeEach(inject(function($controller, _$httpBackend_) {
ctrl = $controller('MainController', {$scope: scope});
$httpBackend = _$httpBackend_;
}));
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
it('should attach an appVersion and registryHost to the scope', function() {
var scopeKeys = Object.keys(scope).sort();
expect(scopeKeys).toEqual(['appVersion', 'registryHost']);
var expectedAppVersion = {"git": {"sha1": "foo", "ref": "bar"}};
var expectedRegistryHost = {"host": "path-to-your-registry", "port": 80};
$httpBackend.expectGET('/app-version.json').respond(expectedAppVersion);
$httpBackend.expectGET('registry-host.json').respond(expectedRegistryHost);
$httpBackend.flush();
jasmine.addCustomEqualityTester(angular.equals);
expect(scope.appVersion).toEqual(expectedAppVersion);
expect(scope.registryHost).toEqual(expectedRegistryHost);
});
});