diff --git a/awx/ui/client/lib/components/layout/layout.directive.js b/awx/ui/client/lib/components/layout/layout.directive.js index 0f40ce7868..0af7746d57 100644 --- a/awx/ui/client/lib/components/layout/layout.directive.js +++ b/awx/ui/client/lib/components/layout/layout.directive.js @@ -18,7 +18,7 @@ function AtLayoutController ($scope, strings) { $scope.$watch('$root.socketStatus', (newStatus) => { vm.socketState = newStatus; - vm.socketIconClass = `icon-socket-${$scope.socketStatus}`; + vm.socketIconClass = `icon-socket-${vm.socketState}`; }); $scope.$watch('$root.licenseMissing', (licenseMissing) => { diff --git a/awx/ui/client/test/unit/index.js b/awx/ui/client/test/unit/index.js index aa4f73ab95..d26d4c6870 100644 --- a/awx/ui/client/test/unit/index.js +++ b/awx/ui/client/test/unit/index.js @@ -1,11 +1,7 @@ // Import angular and angular-mocks to the global scope -import 'angular'; import 'angular-mocks'; -// Import custom Angular module dependencies -import '../../src/i18n'; -import '../../lib/services'; -import '../../lib/components'; - // Import tests -import './panel-body.spec'; +import './layout.spec'; +import './side-nav.spec'; +import './side-nav-item.spec'; \ No newline at end of file diff --git a/awx/ui/client/test/unit/karma.conf.js b/awx/ui/client/test/unit/karma.conf.js index e3343b0a00..b0b33eb6cd 100644 --- a/awx/ui/client/test/unit/karma.conf.js +++ b/awx/ui/client/test/unit/karma.conf.js @@ -1,4 +1,3 @@ -let path = require('path'); const webpackConfig = require('../../../build/webpack.test.js'); module.exports = config => { @@ -11,29 +10,26 @@ module.exports = config => { browsers: ['PhantomJS'], reporters: ['progress'], files: [ - './index.js', - '../../lib/components/**/*.html' + '../../../client/src/vendor.js', + '../../../client/src/app.js', + '../../../client/src/**/*.html', + './index.js', ], plugins: [ 'karma-webpack', 'karma-jasmine', 'karma-phantomjs-launcher', - 'karma-ng-html2js-preprocessor' + 'karma-html2js-preprocessor' ], preprocessors: { - '../../lib/components/**/*.html': 'ng-html2js', + '../../../client/src/vendor.js': 'webpack', + '../../../client/src/app.js': 'webpack', + '../../../client/src/**/*.html': 'html2js', './index.js': 'webpack' }, - ngHtml2JsPreprocessor: { - moduleName: 'at.test.templates', - cacheIdFromPath: function (filepath) { - filepath = filepath.replace(path.join(__dirname, '../../lib'), ''); - return '/static/partials' + filepath; - } - }, webpack: webpackConfig, webpackMiddleware: { noInfo: 'errors-only' } }); -}; +}; \ No newline at end of file diff --git a/awx/ui/client/test/unit/layout.spec.js b/awx/ui/client/test/unit/layout.spec.js new file mode 100644 index 0000000000..512cfbef50 --- /dev/null +++ b/awx/ui/client/test/unit/layout.spec.js @@ -0,0 +1,162 @@ +describe('Components | Layout', () => { + let $compile; + let $rootScope; + let element; + let scope; + let i18n; + + beforeEach(() => { + angular.mock.module('gettext'); + angular.mock.module('I18N'); + angular.mock.module('ui.router'); + angular.mock.module('at.lib.services'); + angular.mock.module('at.lib.components'); + }); + + beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { + $compile = _$compile_; + $rootScope = _$rootScope_; + scope = $rootScope.$new(); + + element = angular.element(''); + element = $compile(element)(scope); + scope.$digest(); + })); + + describe('AtLayoutController', () => { + let controller; + + beforeEach(()=> { + controller = element.controller('atLayout'); + }); + + it('$scope.$on($stateChangeSuccess) should assign toState name to currentState', () => { + let next = {'name': 'dashboard'}; + $rootScope.$broadcast('$stateChangeSuccess', next); + expect(controller.currentState).toBe('dashboard'); + }); + + describe('$root.current_user watcher should assign value to ', () => { + beforeEach(() => { + let val = { + username: 'admin', + id: 1 + }; + $rootScope.current_user = val; + scope.$digest(); + }); + + it('isLoggedIn', () => { + expect(controller.isLoggedIn).toBe('admin'); + + $rootScope.current_user = { id: 1 }; + scope.$digest(); + expect(controller.isLoggedIn).not.toBeDefined(); + }); + + it('isSuperUser', () => { + $rootScope.current_user = 'one'; + $rootScope.user_is_superuser = true; + $rootScope.user_is_system_auditor = false; + scope.$digest(); + expect(controller.isSuperUser).toBe(true); + + $rootScope.current_user = 'two'; + $rootScope.user_is_superuser = false; + $rootScope.user_is_system_auditor = true; + scope.$digest(); + expect(controller.isSuperUser).toBe(true); + + $rootScope.current_user = 'three'; + $rootScope.user_is_superuser = true; + $rootScope.user_is_system_auditor = true; + scope.$digest(); + expect(controller.isSuperUser).toBe(true); + + $rootScope.current_user = 'four'; + $rootScope.user_is_superuser = false; + $rootScope.user_is_system_auditor = false; + scope.$digest(); + expect(controller.isSuperUser).toBe(false); + }); + + it('currentUsername', () => { + expect(controller.currentUsername).toBeTruthy(); + expect(controller.currentUsername).toBe('admin'); + }); + + it('currentUserId', () => { + expect(controller.currentUserId).toBeTruthy(); + expect(controller.currentUserId).toBe(1); + }); + + }); + + describe('$root.socketStatus watcher should assign newStatus to', () => { + let statuses = ['connecting', 'error', 'ok']; + + it('socketState', () => { + _.forEach(statuses, (status) => { + $rootScope.socketStatus = status; + scope.$digest(); + expect(controller.socketState).toBeTruthy(); + expect(controller.socketState).toBe(status); + }); + }); + + it('socketIconClass', () => { + _.forEach(statuses, (status) => { + $rootScope.socketStatus = status; + scope.$digest(); + expect(controller.socketIconClass).toBe(`icon-socket-${status}`); + }); + }); + }); + + describe('$root.licenseMissing watcher should assign true or false to', () => { + it('licenseIsMissing', () => { + $rootScope.licenseMissing = true; + scope.$digest(); + expect(controller.licenseIsMissing).toBe(true); + + $rootScope.licenseMissing = false; + scope.$digest(); + expect(controller.licenseIsMissing).toBe(false); + }); + }); + + describe('getString()', () => { + it('calls ComponentsStrings get() method', angular.mock.inject((_ComponentsStrings_) => { + spyOn(_ComponentsStrings_, 'get'); + controller.getString('VIEW_DOCS') + expect(_ComponentsStrings_.get).toHaveBeenCalled(); + })); + + it('ComponentsStrings get() method should throw an error if string is not a property name of the layout class', () => { + expect(controller.getString.bind(null, 'SUBMISSION_ERROR_TITLE')).toThrow(); + }); + + it('should return layout string', () => { + let layoutStrings = { + CURRENT_USER_LABEL: 'Logged in as', + VIEW_DOCS: 'View Documentation', + LOGOUT: 'Logout', + }; + + _.forEach(layoutStrings, (value, key) => { + expect(controller.getString(key)).toBe(value); + }); + }); + + it('should return default string', () => { + let defaultStrings = { + BRAND_NAME: "AWX" + }; + + _.forEach(defaultStrings, (value, key) => { + expect(controller.getString(key)).toBe(value); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/awx/ui/client/test/unit/panel-body.spec.js b/awx/ui/client/test/unit/panel-body.spec.js deleted file mode 100644 index 3915195847..0000000000 --- a/awx/ui/client/test/unit/panel-body.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -describe('Components | panel/body', () => { - - let $compile; - let $rootScope; - - beforeEach(() => { - angular.mock.module('at.lib.services') - angular.mock.module('at.lib.components') - angular.mock.module('at.test.templates'); - }); - - beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - })); - - it('Should produce at-Panel-body HTML content', () => { - let element = $compile('yo')($rootScope); - $rootScope.$digest(); - - expect(element.hasClass('at-Panel-body')).toBe(true); - expect(element.html()).toContain('yo'); - }); -}); diff --git a/awx/ui/client/test/unit/side-nav-item.spec.js b/awx/ui/client/test/unit/side-nav-item.spec.js new file mode 100644 index 0000000000..f605a40e69 --- /dev/null +++ b/awx/ui/client/test/unit/side-nav-item.spec.js @@ -0,0 +1,61 @@ +describe('Components | Side Nav Item', () => { + let $compile; + let $rootScope; + let element; + let scope; + + beforeEach(() => { + angular.mock.module('gettext'); + angular.mock.module('I18N'); + angular.mock.module('ui.router'); + angular.mock.module('at.lib.services') + angular.mock.module('at.lib.components') + }); + + beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { + $compile = _$compile_; + $rootScope = _$rootScope_; + scope = $rootScope.$new(); + + element = angular.element(''); + element = $compile(element)(scope); + scope.name = 'dashboard'; + scope.$digest(); + })); + + describe('Side Nav Item Controller', () => { + let LayoutCtrl; + let SideNavItem; + let SideNavItemCtrl; + + beforeEach(() => { + SideNavItem = angular.element(element[0].querySelector('at-side-nav-item')); + SideNavItemCtrl = SideNavItem.controller('atSideNavItem'); + }); + + it('layoutVm.currentState watcher should assign isRoute', () => { + let current = {'name': 'dashboard'}; + $rootScope.$broadcast('$stateChangeSuccess', current); + scope.$digest(); + expect(SideNavItemCtrl.isRoute).toBe(true); + + current = {'name': 'inventories'}; + $rootScope.$broadcast('$stateChangeSuccess', current); + scope.$digest(); + expect(SideNavItemCtrl.isRoute).toBe(false); + }); + + it('go() should call $state.go()', angular.mock.inject((_$state_) => { + spyOn(_$state_, 'go'); + SideNavItemCtrl.go(); + expect(_$state_.go).toHaveBeenCalled(); + expect(_$state_.go).toHaveBeenCalledWith('dashboard', jasmine.any(Object), jasmine.any(Object)); + })); + + it('should load name, icon, and route from scope', () => { + expect(SideNavItem.isolateScope().name).toBeDefined(); + expect(SideNavItem.isolateScope().iconClass).toBeDefined(); + expect(SideNavItem.isolateScope().route).toBeDefined(); + }); + }); +}); diff --git a/awx/ui/client/test/unit/side-nav.spec.js b/awx/ui/client/test/unit/side-nav.spec.js new file mode 100644 index 0000000000..1342e41659 --- /dev/null +++ b/awx/ui/client/test/unit/side-nav.spec.js @@ -0,0 +1,78 @@ +describe('Components | Side Nav', () => { + let $compile; + let $rootScope; + let element; + let scope; + let windowMock = { + innerWidth: 500 + }; + + beforeEach(() => { + angular.mock.module('gettext'); + angular.mock.module('I18N'); + angular.mock.module('ui.router'); + angular.mock.module('at.lib.services') + angular.mock.module('at.lib.components', ($provide) => { + $provide.value('$window', windowMock); + }); + }); + + beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { + $compile = _$compile_; + $rootScope = _$rootScope_; + scope = $rootScope.$new(); + + element = angular.element(""); + element = $compile(element)(scope); + scope.$digest(); + })); + + describe('Side Nav Controller', () => { + let sideNav; + let sideNavCtrl; + + beforeEach(() => { + sideNav = angular.element(element[0].querySelector('.at-Layout-side')); + sideNavCtrl = sideNav.controller('atSideNav'); + }); + + it('isExpanded defaults to false', () => { + expect(sideNavCtrl.isExpanded).toBe(false); + }); + + it('toggleExpansion()', () => { + expect(sideNavCtrl.isExpanded).toBe(false); + + sideNavCtrl.toggleExpansion(); + expect(sideNavCtrl.isExpanded).toBe(true); + + sideNavCtrl.toggleExpansion(); + expect(sideNavCtrl.isExpanded).toBe(false); + + sideNavCtrl.toggleExpansion(); + expect(sideNavCtrl.isExpanded).toBe(true); + + sideNavCtrl.toggleExpansion(); + expect(sideNavCtrl.isExpanded).toBe(false); + }); + + it('isExpanded should be false after state change event', () => { + sideNavCtrl.isExpanded = true; + + let current = { + 'name': 'dashboard' + }; + $rootScope.$broadcast('$stateChangeSuccess', current); + scope.$digest(); + expect(sideNavCtrl.isExpanded).toBe(false); + }); + + it('clickOutsideSideNav watcher should assign isExpanded to false', () => { + sideNavCtrl.isExpanded = true; + + $rootScope.$broadcast('clickOutsideSideNav'); + scope.$digest(); + expect(sideNavCtrl.isExpanded).toBe(false); + }); + }); +}); diff --git a/awx/ui/package.json b/awx/ui/package.json index 3cb89bcec5..e6893ae15c 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -25,7 +25,7 @@ "jshint": "grunt jshint:source --no-color", "test:ci": "npm run test -- --single-run --reporter junit,dots --browsers=PhantomJS", "e2e": "./client/test/e2e/runner.js --config ./client/test/e2e/nightwatch.conf.js", - "component-test": "karma start client/test/karma.conf.js", + "component-test": "karma start client/test/unit/karma.conf.js", "lint": "eslint -c .eslintrc.js .", "dev": "webpack --config build/webpack.development.js --progress", "watch": "webpack-dev-server --config build/webpack.watch.js --progress", @@ -70,7 +70,6 @@ "karma-html2js-preprocessor": "^1.0.0", "karma-jasmine": "^1.1.0", "karma-junit-reporter": "^1.2.0", - "karma-ng-html2js-preprocessor": "^1.0.0", "karma-phantomjs-launcher": "^1.0.2", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.4",