Preparation to bower release
diff --git a/spec/ui/alert.test.js b/spec/ui/alert.test.js
new file mode 100644
index 0000000..c67d03a
--- /dev/null
+++ b/spec/ui/alert.test.js
@@ -0,0 +1,131 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 4/15/16.
+ */
+
+(function () {
+ 'use strict';
+
+ describe('The xos.helper module', function(){
+ describe('The xos-alert component', () => {
+
+ let element, scope, isolatedScope;
+
+ let message = 'Test Error Message';
+
+ beforeEach(module('xos.helpers'));
+
+ it('should throw an error if no config is specified', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ $compile(angular.element('<xos-alert></xos-alert>'))($rootScope);
+ $rootScope.$digest();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosAlert] Please provide a configuration via the "config" attribute'));
+ }));
+
+ describe('when correctly configured', () => {
+ beforeEach(inject(($compile, $rootScope) => {
+
+ scope = $rootScope.$new();
+
+ scope.config = {
+ type: 'danger',
+ closeBtn: true
+ };
+
+ element = angular.element(`<xos-alert config="config">${message}</xos-alert>`);
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should transclude the message', () => {
+ let textContainer = element[0].getElementsByTagName('p')[0];
+ let text = angular.element(textContainer).text();
+ expect(text).toEqual(message)
+ });
+
+ it('should have a close button', () => {
+ let btn = element[0].getElementsByTagName('button');
+ expect(btn.length).toEqual(1);
+ });
+
+ describe('when the close button is clicked', () => {
+ it('should hide the alert', () => {
+ let btn = element[0].getElementsByTagName('button')[0];
+ btn.click();
+ let alert = angular.element(element[0].querySelectorAll('.alert')[0]);
+ expect(alert.hasClass('ng-hide')).toBeTruthy();
+ });
+ });
+
+ describe('when autoHide is set', () => {
+
+ let to;
+
+ beforeEach(inject(($compile, $rootScope, $timeout) => {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ type: 'danger',
+ closeBtn: true,
+ autoHide: 500
+ };
+
+ to = $timeout;
+
+ element = angular.element(`<xos-alert config="config">${message}</xos-alert>`);
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should hide the alert', () => {
+ to.flush();
+ expect(isolatedScope.show).toBeFalsy();
+ let alert = angular.element(element[0].querySelectorAll('.alert')[0]);
+ expect(alert.hasClass('ng-hide')).toBeTruthy();
+ });
+ });
+
+ describe('when show is set to false', () => {
+
+ beforeEach(inject(($compile, $rootScope) => {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ type: 'danger',
+ closeBtn: true
+ };
+
+ scope.show = false;
+
+ element = angular.element(`<xos-alert config="config" show="show">${message}</xos-alert>`);
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should hide the alert', () => {
+ let alert = angular.element(element[0].querySelectorAll('.alert')[0]);
+ expect(alert.hasClass('ng-hide')).toBeTruthy();
+ });
+
+ describe('when show is changed to true', () => {
+ beforeEach(() => {
+ scope.show = true;
+ scope.$digest();
+ });
+
+ it('should show the alert', () => {
+ let alert = angular.element(element[0].querySelectorAll('.alert')[0]);
+ expect(alert.hasClass('ng-hide')).toBeFalsy();
+ });
+ });
+ });
+
+ });
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/custom-validator.test.js b/spec/ui/custom-validator.test.js
new file mode 100644
index 0000000..c85c934
--- /dev/null
+++ b/spec/ui/custom-validator.test.js
@@ -0,0 +1,107 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 5/25/16.
+ */
+
+(function () {
+ 'use strict';
+
+ describe('The xos.helper module', function () {
+ describe('The xosCustomValidator directive', () => {
+ let element, scope, isolatedScope, rootScope, compile, form, input;
+ const compileElement = (el) => {
+ element = el;
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+ if(angular.isUndefined(element)){
+ element = angular.element(`
+ <form name="form">
+ <input name="testInput" type="text" ng-model="value" xos-custom-validator custom-validator="validator"/>
+ </form>
+ `);
+ }
+ compile(element)(scope);
+ scope.$digest();
+ input = $(element).find('input');
+ isolatedScope = angular.element(input).isolateScope();
+ form = scope.form;
+ };
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(inject(function ($compile, $rootScope) {
+ compile = $compile;
+ rootScope = $rootScope;
+ }));
+
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.validator = 'validator';
+ scope.value = '';
+ compileElement();
+ });
+
+ it('should bind the validator', () => {
+ expect(isolatedScope.fn).toEqual('validator');
+ });
+
+ describe('given a validator function', () => {
+
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.value = '';
+ scope.validator = (model) => angular.equals(model, 'test');
+ spyOn(scope, 'validator').and.callThrough();
+ compileElement();
+ });
+
+ it('should call the validator function on value change', () => {
+ form.testInput.$setViewValue('something');
+ scope.$digest();
+ expect(scope.validator).toHaveBeenCalledWith('something');
+ expect(scope.value).toEqual('something');
+ });
+
+ it('should set the field invalid', () => {
+ form.testInput.$setViewValue('something');
+ scope.$digest();
+ expect(scope.validator).toHaveBeenCalledWith('something');
+ expect(input).toHaveClass('ng-invalid');
+ expect(input).toHaveClass('ng-invalid-custom');
+ });
+
+ it('should set the field valid', () => {
+ form.testInput.$setViewValue('test');
+ scope.$digest();
+ expect(scope.validator).toHaveBeenCalledWith('test');
+ expect(input).not.toHaveClass('ng-invalid');
+ expect(input).not.toHaveClass('ng-invalid-custom');
+ });
+
+ describe('if the validation function return an array', () => {
+
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.value = '';
+ scope.validator = (model) => {
+ return ['randomTest', angular.equals(model, 'test')];
+ };
+ spyOn(scope, 'validator').and.callThrough();
+ compileElement();
+ });
+
+ it('should set the field invalid', () => {
+ form.testInput.$setViewValue('something');
+ scope.$digest();
+ expect(scope.validator).toHaveBeenCalledWith('something');
+ expect(input).toHaveClass('ng-invalid');
+ expect(input).toHaveClass('ng-invalid-random-test');
+ });
+ });
+ });
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/field.test.js b/spec/ui/field.test.js
new file mode 100644
index 0000000..bfb36a3
--- /dev/null
+++ b/spec/ui/field.test.js
@@ -0,0 +1,330 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 5/25/16.
+ */
+
+(function () {
+ 'use strict';
+
+ describe('The xos.helper module', function(){
+
+ describe('The xosField component', () => {
+ let element, scope, isolatedScope, rootScope, compile;
+ const compileElement = (el) => {
+ element = el;
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+ if(angular.isUndefined(element)){
+ element = angular.element('<xos-field name="name" field="field" ng-model="ngModel"></xos-field>');
+ }
+ compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ };
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(inject(function ($compile, $rootScope) {
+ compile = $compile;
+ rootScope = $rootScope;
+ }));
+
+ it('should throw an error if no name is passed', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.field = {
+ label: 'Label',
+ type: 'number',
+ validators: {}
+ };
+ scope.ngModel = 1;
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide a field name'));
+ }));
+
+ it('should throw an error if no field definition is passed', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.name = 'label';
+ scope.ngModel = 1;
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide a field definition'));
+ }));
+
+ it('should throw an error if no field type is passed', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.name = 'label';
+ scope.ngModel = 1;
+ scope.field = {label: 'Label:'}
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide a type in the field definition'));
+ }));
+
+ it('should throw an error if no field model is passed', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'number',
+ validators: {}
+ };
+ compileElement(angular.element('<xos-field name="name" field="field"></xos-field>'));
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide an ng-model'));
+ }));
+
+ describe('when a text input is passed', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'text',
+ validators: {
+ custom: 'fake'
+ }
+ };
+ scope.ngModel = 'label';
+ compileElement();
+ });
+
+ it('should print a text field', () => {
+ expect($(element).find('[name="label"]')).toHaveAttr('type', 'text');
+ });
+
+ it('should attach the custom validator directive', () => {
+ let input = $(element).find('[name="label"]');
+ expect(input).toHaveAttr('xos-custom-validator');
+ expect(input).toHaveAttr('custom-validator', 'vm.field.validators.custom || null');
+ });
+ });
+
+ describe('when a option is selected in dropdown', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'select',
+ validators: {},
+ options: [
+ {
+ id: 0,
+ label: '---Site---'
+ },
+ {
+ id: 1,
+ label: '---Site1---'
+ }
+ ]
+ };
+ scope.ngModel = 0;
+ compileElement();
+ });
+
+ it('No of select elements', () => {
+ expect($(element).find('select').children('option').length).toEqual(2);
+ });
+
+ it('should show the selected value', () => {
+ const elem = angular.element($(element).find('select').children('option')[0]);
+ expect(elem.text()).toEqual('---Site---');
+ expect(elem).toHaveAttr('selected');
+ });
+ });
+
+ describe('when a number input is passed', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'number',
+ validators: {}
+ };
+ scope.ngModel = 10;
+ compileElement();
+ });
+
+ it('should print a number field', () => {
+ expect($(element).find('[name="label"]')).toHaveAttr('type', 'number');
+ });
+ });
+
+ describe('when a boolean input is passed', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'boolean',
+ validators: {}
+ };
+ scope.ngModel = true;
+ compileElement();
+ });
+
+ let setFalse, setTrue;
+
+ beforeEach(() => {
+ setFalse= $(element).find('.boolean-field > a:first-child');
+ setTrue = $(element).find('.boolean-field > a:last-child');
+ });
+
+ it('should print two buttons', () => {
+ expect($(element).find('.boolean-field > a').length).toEqual(2)
+ });
+
+ it('should change value to false', () => {
+ expect(isolatedScope.ngModel).toEqual(true);
+ clickElement(setFalse[0]);
+ expect(isolatedScope.ngModel).toEqual(false);
+ });
+
+ it('should change value to true', () => {
+ isolatedScope.ngModel = false;
+ scope.$apply();
+ expect(isolatedScope.ngModel).toEqual(false);
+ clickElement(setTrue[0]);
+ expect(isolatedScope.ngModel).toEqual(true);
+ });
+ });
+
+ describe('when an object input is passed', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'object',
+ validators: {}
+ };
+ scope.ngModel = {
+ baz: true,
+ foo: 'bar',
+ foz: 1,
+ };
+ compileElement();
+ });
+
+ it('should print a panel to contain object property field', () => {
+ expect($(element).find('.panel.object-field')).toExist()
+ });
+
+ it('should print the right input type for each property', () => {
+ expect($(element).find('input').length).toBe(2);
+ expect($(element).find('.boolean-field > a').length).toEqual(2);
+ });
+
+ it('should format labels', () => {
+ expect($(element).find('input[name="foo"]').parent().find('label').text()).toBe('Foo:');
+ });
+
+ describe('and the model is empty', () => {
+ beforeEach(() => {
+ scope.ngModel = {
+ };
+ compileElement();
+ });
+
+ it('should not print the panel', () => {
+ expect($(element).find('.panel.object-field')).not.toExist()
+ });
+
+ describe('but field is configured', () => {
+ beforeEach(() => {
+ scope.field.properties = {
+ foo: {
+ label: 'FooLabel:',
+ type: 'string',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ };
+ compileElement();
+ });
+ it('should render panel and configured fields', () => {
+ expect($(element).find('.panel.object-field')).toExist();
+ expect($(element).find('input[name="foo"]').parent().find('label').text()).toBe('FooLabel:');
+ expect($(element).find('input[name="foo"]')).toHaveAttr('type', 'string');
+ expect($(element).find('input[name="foo"]')).toHaveAttr('required');
+
+ expect($(element).find('input[name="bar"]').parent().find('label').text()).toBe('Bar:');
+ expect($(element).find('input[name="bar"]')).toHaveAttr('type', 'number');
+
+ });
+ });
+ });
+ });
+
+ describe('when validation options are passed', () => {
+ let input;
+ describe('given a a text field', () => {
+ beforeEach(() => {
+ scope.field = {
+ label: 'Label',
+ type: 'text',
+ validators: {
+ minlength: 10,
+ maxlength: 15,
+ required: true
+ }
+ };
+
+ scope.$digest();
+ input = $(element).find('input');
+ });
+
+ it('should validate required', () => {
+ scope.ngModel= null;
+ scope.$digest();
+ expect(input).toHaveClass('ng-invalid-required');
+
+ scope.ngModel= 'not too short';
+ scope.$digest();
+ expect(input).not.toHaveClass('ng-invalid-required');
+ expect(input).not.toHaveClass('ng-invalid');
+ });
+
+ it('should validate minlength', () => {
+ scope.ngModel= 'short';
+ scope.$digest();
+ expect(input).toHaveClass('ng-invalid-minlength');
+
+ scope.ngModel= 'not too short';
+ scope.$digest();
+ expect(input).not.toHaveClass('ng-invalid-minlength');
+ expect(input).not.toHaveClass('ng-invalid');
+ });
+
+ it('should validate maxlength', () => {
+ scope.ngModel= 'this is definitely too long!!';
+ scope.$digest();
+ expect(input).toHaveClass('ng-invalid-maxlength');
+
+ scope.ngModel= 'not too short';
+ scope.$digest();
+ expect(input).not.toHaveClass('ng-invalid-maxlength');
+ expect(input).not.toHaveClass('ng-invalid');
+ });
+ });
+ });
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/form.test.js b/spec/ui/form.test.js
new file mode 100644
index 0000000..e92d31a
--- /dev/null
+++ b/spec/ui/form.test.js
@@ -0,0 +1,269 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 4/18/16.
+ */
+
+(function () {
+ 'use strict';
+
+ let element, scope, isolatedScope, rootScope, compile;
+
+ const compileElement = () => {
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+
+ element = angular.element(`<xos-form config="config" ng-model="model"></xos-form>`);
+ compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }
+
+ describe('The xos.helper module', function(){
+
+ describe('The xos-form component', () => {
+
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(inject(($compile, $rootScope) => {
+ rootScope = $rootScope;
+ compile = $compile;
+ }));
+
+ it('should throw an error if no config is specified', () => {
+ function errorFunctionWrapper(){
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosForm] Please provide a configuration via the "config" attribute'));
+ });
+
+ it('should throw an error if no actions is specified', () => {
+ function errorFunctionWrapper(){
+ scope = rootScope.$new();
+ scope.config = 'green';
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosForm] Please provide an action list in the configuration'));
+ });
+
+ describe('when correctly configured', () => {
+
+ let cb = jasmine.createSpy('callback');
+
+ beforeEach(inject(($rootScope) => {
+
+ scope = $rootScope.$new();
+
+ scope.config = {
+ exclude: ['excludedField'],
+ formName: 'testForm',
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: cb,
+ class: 'success'
+ }
+ ],
+ fields: {
+ first_name: {
+ label: 'Custom Label'
+ }
+ }
+ };
+
+ scope.model = {
+ id: 1,
+ first_name: 'Jhon',
+ last_name: 'Snow',
+ age: 25,
+ email: 'test@onlab.us',
+ birthDate: '2016-04-18T23:44:16.883181Z',
+ enabled: true,
+ role: 'user', //select
+ a_permissions: [
+ ],
+ object_field: {
+ string: 'bar',
+ number: 1,
+ email: 'teo@onlab.us'
+ }
+ };
+
+ compileElement();
+ }));
+
+ it('should add excluded properties to the list', () => {
+ let expected = ['id', 'validators', 'created', 'updated', 'deleted', 'backend_status', 'excludedField'];
+ expect(isolatedScope.excludedField).toEqual(expected);
+ });
+
+ it('should render 10 input field', () => {
+ // boolean are in the form model, but are not input
+ expect(Object.keys(isolatedScope.formField).length).toEqual(9);
+ const field = element[0].getElementsByTagName('input');
+ expect(field.length).toEqual(10);
+ });
+
+ it('should render 1 boolean field', () => {
+ expect($(element).find('.boolean-field > a').length).toEqual(2)
+ });
+
+ it('when clicking on action should invoke callback', () => {
+ const link = $(element).find('[role="button"]');
+ //console.log(link);
+ link.click();
+ // TODO : Check correct parameters
+ expect(cb).toHaveBeenCalled();
+
+ });
+
+ it('should set a custom label', () => {
+ let nameField = element[0].getElementsByClassName('form-group')[0];
+ let label = angular.element(nameField.getElementsByTagName('label')[0]).text()
+ expect(label).toEqual('Custom Label:');
+ });
+
+ it('should use the correct input type', () => {
+ expect($(element).find('[name="age"]')).toHaveAttr('type', 'number');
+ expect($(element).find('[name="birthDate"]')).toHaveAttr('type', 'date');
+ expect($(element).find('[name="email"]')).toHaveAttr('type', 'email');
+ });
+
+ xdescribe('the boolean field test', () => {
+
+ let setFalse, setTrue;
+
+ beforeEach(() => {
+ setFalse= $(element).find('.boolean-field > button:first-child');
+ setTrue = $(element).find('.boolean-field > button:last-child');
+ });
+
+ it('should change value to false', () => {
+ expect(isolatedScope.ngModel.enabled).toEqual(true);
+ setFalse.click();
+ expect(isolatedScope.ngModel.enabled).toEqual(false);
+ });
+
+ it('should change value to true', () => {
+ isolatedScope.ngModel.enabled = false;
+ scope.$apply();
+ expect(isolatedScope.ngModel.enabled).toEqual(false);
+ setTrue.click()
+ expect(isolatedScope.ngModel.enabled).toEqual(true);
+ });
+ });
+
+ describe('when a deep model is passed', () => {
+
+ beforeEach(inject(($rootScope) => {
+
+ scope = $rootScope.$new();
+
+ scope.config = {
+ exclude: ['excludedField'],
+ formName: 'testForm',
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: cb,
+ class: 'success'
+ }
+ ],
+ fields: {
+ object_field: {
+ field_one: {
+ label: 'Custom Label'
+ }
+ }
+ }
+ };
+
+ scope.model = {
+ object_field: {
+ field_one: 'bar',
+ number: 1,
+ email: 'teo@onlab.us'
+ }
+ };
+
+ compileElement();
+ }));
+
+ it('should print nested field', () => {
+ expect($(element).find('input').length).toBe(3);
+ });
+
+ xit('should configure nested fields', () => {
+ let custom_label = $(element).find('input[name=field_one]').parent().find('label');
+ expect(custom_label.text()).toBe('Custom Label');
+ });
+ });
+ });
+ describe('when correctly configured for feedback', () => {
+
+ let fb = jasmine.createSpy('feedback').and.callFake(function(statusFlag) {
+ if(statusFlag){
+ scope.config.feedback.show = true;
+ scope.config.feedback.message = 'Form Submitted';
+ scope.config.feedback.type = 'success';
+ }
+ else {
+ scope.config.feedback.show = true;
+ scope.config.feedback.message = 'Error';
+ scope.config.feedback.type = 'danger';
+
+ }
+ });
+
+ beforeEach(()=> {
+ scope = rootScope.$new();
+ scope.config =
+ {
+
+ feedback: {
+ show: false,
+ message: 'Form submitted successfully !!!',
+ type: 'success'
+ },
+ actions: [
+ {
+ label: 'Save',
+ icon: 'ok', // refers to bootstraps glyphicon
+ cb: () => {},
+ class: 'success'
+ }
+ ]
+ };
+ scope.model={};
+ compileElement();
+ });
+
+ it('should not show feedback when loaded', () => {
+ expect($(element).find('xos-alert > div')).toHaveClass('alert alert-success ng-hide');
+ });
+
+ it('should show a success feedback', () => {
+ fb(true);
+ scope.$digest();
+ expect(isolatedScope.config.feedback.type).toEqual('success');
+ expect(fb).toHaveBeenCalledWith(true);
+ expect($(element).find('xos-alert > div')).toHaveClass('alert alert-success');
+ });
+
+ it('should show an error feedback', function() {
+ fb(false);
+ scope.$digest();
+ expect(isolatedScope.config.feedback.type).toEqual('danger');
+ expect(fb).toHaveBeenCalledWith(false);
+ expect($(element).find('xos-alert > div')).toHaveClass('alert alert-danger');
+ });
+ });
+
+ });
+ });
+})();
diff --git a/spec/ui/pagination.test.js b/spec/ui/pagination.test.js
new file mode 100644
index 0000000..03f1045
--- /dev/null
+++ b/spec/ui/pagination.test.js
@@ -0,0 +1,53 @@
+(function () {
+ 'use strict';
+
+ describe('The xos.helper module', function(){
+ describe('The xos-pagination component', () => {
+
+ let scope, element, isolatedScope;
+ let cb = jasmine.createSpy('callback')
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(inject(function ($compile, $rootScope) {
+ scope = $rootScope.$new();
+
+ scope.pageSize = 2;
+
+ scope.totalElements = 5;
+
+ scope.change = cb;
+
+ element = angular.element('<xos-pagination page-size="pageSize" total-elements="totalElements" change="change"></xos-table>');
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should contain 3 pages', function() {
+ const li = element[0].getElementsByTagName('li');
+ expect(li.length).toEqual(5);
+ });
+
+ it('should call the change function', () => {
+ const li = element[0].getElementsByTagName('li')[3];
+ let link = li.getElementsByTagName('a')[0];
+ link.click();
+ expect(cb).toHaveBeenCalledWith(2);
+ });
+
+ describe('when elements number is less than page size', () => {
+ beforeEach(() => {
+ isolatedScope.pageSize = 10;
+ isolatedScope.totalElements = 9;
+ scope.$digest();
+ });
+
+ it('should not be rendered', () => {
+ const pagination = element[0].getElementsByClassName('pagination');
+ expect(pagination.length).toEqual(0);
+ });
+ });
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/smart-pie.test.js b/spec/ui/smart-pie.test.js
new file mode 100644
index 0000000..d5a9cfe
--- /dev/null
+++ b/spec/ui/smart-pie.test.js
@@ -0,0 +1,210 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ let mockData, compile, rootScope, spy, scope, isolatedScope, element, interval;
+
+ const compileElement = () => {
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+
+ element = angular.element('<xos-smart-pie config="config"></xos-smart-pie>');
+ compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }
+
+ describe('The xos.helper module', function(){
+ describe('The xos-smart-pie component', () => {
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(function(){
+ module(function($provide){
+ $provide.service('MockResource', function(){
+ return {
+ query: ''
+ }
+ });
+ });
+ });
+
+ beforeEach(inject(function ($compile, $rootScope) {
+
+ // set mockData
+ mockData = [
+ {
+ id: 1,
+ first_name: 'Jon',
+ last_name: 'Snow',
+ category: 1
+ },
+ {
+ id: 2,
+ first_name: 'Danaerys',
+ last_name: 'Targaryen',
+ category: 2
+ },
+ {
+ id: 3,
+ first_name: 'Aria',
+ last_name: 'Stark',
+ category: 1
+ }
+ ]
+
+ compile = $compile;
+ rootScope = $rootScope;
+ }));
+
+ it('should throw an error if no resource and no data are passed in the config', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.config = {};
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosSmartPie] Please provide a resource or an array of data in the configuration'));
+ }));
+
+ describe('when data are passed in the configuration', () => {
+ beforeEach(inject(function ($compile, $rootScope) {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ data: mockData,
+ groupBy: 'category',
+ classes: 'my-test-class'
+ };
+
+ compileElement();
+ }));
+
+
+ it('should attach provided classes', () => {
+ expect($(element).find('canvas')).toHaveClass('my-test-class');
+ });
+
+ it('should group elements', () => {
+ let groupedData = [2, 1];
+ expect(isolatedScope.data).toEqual(groupedData);
+ });
+
+ describe('when a labelFormatter function is provided', () => {
+ beforeEach(() => {
+ scope.config.labelFormatter = (labels) => {
+ return labels.map(l => l === '1' ? 'First' : 'Second');
+ };
+ compileElement();
+ });
+ it('should format labels', () => {
+ expect(isolatedScope.labels).toEqual(['First', 'Second'])
+ });
+ });
+
+ describe('when provided data changes', () => {
+ beforeEach(() => {
+ scope.config.data.push({
+ id: 2,
+ first_name: 'Danaerys',
+ last_name: 'Targaryen',
+ category: 1
+ });
+ scope.$digest();
+ });
+ it('should calculate again the data', () => {
+ expect(isolatedScope.data).toEqual([3, 1]);
+ });
+ });
+ });
+
+
+ describe('when a resource is specified in the configuration', () => {
+
+ beforeEach(inject(function ($compile, $rootScope, $q, MockResource) {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ resource: 'MockResource',
+ groupBy: 'category',
+ classes: 'my-test-class'
+ };
+
+ spy = MockResource;
+
+ spyOn(MockResource, 'query').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve(mockData);
+ return {$promise: deferred.promise};
+ });
+
+ compileElement();
+ }));
+
+
+ it('should call the server and group elements', () => {
+ let groupedData = [2, 1];
+ expect(spy.query).toHaveBeenCalled();
+ expect(isolatedScope.data).toEqual(groupedData);
+ });
+
+ describe('when a labelFormatter function is provided', () => {
+ beforeEach(inject(function ($compile, $rootScope){
+ scope = $rootScope.$new();
+ scope.config = {
+ resource: 'MockResource',
+ groupBy: 'category',
+ classes: 'label-formatter-test',
+ labelFormatter: (labels) => {
+ return labels.map(l => l === '1' ? 'First' : 'Second');
+ }
+ };
+ compileElement();
+ }));
+
+ it('should format labels', () => {
+ expect(isolatedScope.labels).toEqual(['First', 'Second'])
+ });
+ });
+
+ describe('when polling is enabled', () => {
+ beforeEach(inject(function ($compile, $rootScope, $interval){
+
+ //mocked $interval (by ngMock)
+ interval = $interval;
+
+ // cleaning the spy
+ spy.query.calls.reset()
+
+ scope = $rootScope.$new();
+ scope.config = {
+ resource: 'MockResource',
+ groupBy: 'category',
+ classes: 'label-formatter-test',
+ poll: 2
+ };
+ compileElement();
+ }));
+
+ it('should call the backend every 2 second', () => {
+ expect(spy.query).toHaveBeenCalled();
+ expect(spy.query.calls.count()).toEqual(1);
+ interval.flush(2000);
+ expect(spy.query.calls.count()).toEqual(2);
+ interval.flush(2000);
+ expect(spy.query.calls.count()).toEqual(3)
+ });
+ });
+ });
+
+
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/smart-table.test.js b/spec/ui/smart-table.test.js
new file mode 100644
index 0000000..e87e807
--- /dev/null
+++ b/spec/ui/smart-table.test.js
@@ -0,0 +1,198 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ let mockData;
+
+ describe('The xos.helper module', function(){
+ describe('The xos-smart-table component', () => {
+
+ let spy, emptySpy, scope, isolatedScope, element;
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(function() {
+
+ // set mockData
+ mockData = [
+ {
+ id: 1,
+ first_name: 'Jon',
+ last_name: 'Snow',
+ hidden_field: 'hidden'
+ }
+ ];
+ });
+
+ // mock the service
+ beforeEach(function(){
+ module(function($provide){
+ $provide.service('MockResource', function(){
+ return {
+ query: '',
+ delete: ''
+ }
+ });
+
+ $provide.service('EmptyResource', function(){
+ return {
+ query: ''
+ }
+ });
+ });
+ })
+
+ beforeEach(inject(function ($compile, $rootScope, $q, MockResource) {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ resource: 'MockResource',
+ hiddenFields: ['hidden_field']
+ };
+
+ spy = MockResource;
+
+ spyOn(MockResource, 'query').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve(mockData);
+ return {$promise: deferred.promise};
+ });
+
+ spyOn(MockResource, 'delete').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve();
+ return {$promise: deferred.promise};
+ });
+
+ element = angular.element('<xos-smart-table config="config"></xos-smart-table>');
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should query elements', () => {
+ expect(spy.query).toHaveBeenCalled();
+ expect($(element).find('.alert').parent().parent()).toHaveClass('ng-hide');
+ });
+
+ it('should hide hidden fields', () => {
+ // the 4th field is the mocked save method
+ expect($(element).find('thead th').length).toEqual(3);
+ expect($(element).find('thead th')[0]).toContainText('First name:');
+ expect($(element).find('thead th')[1]).toContainText('Last name:');
+ expect($(element).find('thead th')[2]).toContainText('Actions:');
+ });
+
+ it('should delete a model', () => {
+ // saving mockData (they are going to be deleted)
+ let mock = angular.copy(mockData);
+ $(element).find('a[title="delete"]')[0].click();
+ expect(spy.delete).toHaveBeenCalledWith({id: mock[0].id});
+ expect($(element).find('.alert')).toContainText(`MockResource with id ${mock[0].id} successfully deleted`);
+ });
+
+ it('should show the form', () => {
+ expect($(element).find('.panel')[0]).toHaveClass('ng-hide');
+ $(element).find('a[title="details"]')[0].click();
+ expect($(element).find('.panel')).not.toHaveClass('ng-hide');
+ });
+
+ it('should hide the form', () => {
+ isolatedScope.detailedItem = {
+ some: 'model'
+ };
+ scope.$apply();
+ expect($(element).find('.panel')).not.toHaveClass('ng-hide');
+ $(element).find('.panel .col-xs-1 a')[0].click();
+ expect($(element).find('.panel')[0]).toHaveClass('ng-hide');
+ });
+
+ it('should save an item', inject(($q) => {
+
+ let model = {
+ id: 1,
+ first_name: 'Jon',
+ last_name: 'Snow',
+ hidden_field: 'hidden',
+ $save: '',
+ $update: ''
+ };
+
+ spyOn(model, '$save').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve();
+ return deferred.promise;
+ });
+
+ spyOn(model, '$update').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve();
+ return deferred.promise;
+ });
+
+ isolatedScope.detailedItem = model;
+ scope.$apply();
+ $(element).find('xos-form .btn.btn-success').click();
+ expect(model.$update).toHaveBeenCalled();
+ }));
+
+ it('should have an add button', () => {
+ let addBtn = $(element).find('.row .btn.btn-success');
+ expect(addBtn.parent().parent()).not.toHaveClass('ng-hide');
+ });
+
+ describe('when the add button is clicked', () => {
+ beforeEach(() => {
+ let btn = $(element).find('.row .btn.btn-success')
+ btn[0].click();
+ });
+
+ xit('should create a new model', () => {
+ expect(isolatedScope.detailedItem).toBeDefined();
+ expect(isolatedScope.detailedItem).toBeInstanceOf('Resource');
+ });
+ });
+
+ describe('when fetching an empty collection', () => {
+ beforeEach(inject(function ($compile, $rootScope, $q, EmptyResource) {
+ scope = $rootScope.$new();
+
+ scope.config = {
+ resource: 'EmptyResource'
+ };
+
+ emptySpy = EmptyResource;
+
+ spyOn(EmptyResource, 'query').and.callFake(function() {
+ const deferred = $q.defer();
+ deferred.resolve([]);
+ return {$promise: deferred.promise};
+ });
+
+ element = angular.element('<xos-smart-table config="config"></xos-smart-table>');
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should display an alert', () => {
+ expect(emptySpy.query).toHaveBeenCalled();
+ expect($(element).find('.alert').parent().parent()).not.toHaveClass('ng-hide');
+ expect($(element).find('.alert')).toContainText('No data to show');
+ });
+
+ it('should not have an add button', () => {
+ let addBtn = $(element).find('.row .btn.btn-success');
+ expect(addBtn.parent().parent()).toHaveClass('ng-hide');
+ });
+ });
+
+
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/table.test.js b/spec/ui/table.test.js
new file mode 100644
index 0000000..0b6ea87
--- /dev/null
+++ b/spec/ui/table.test.js
@@ -0,0 +1,576 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 3/24/16.
+ */
+
+(function () {
+ 'use strict';
+
+ let scope, element, isolatedScope, rootScope, compile, filter;
+ const compileElement = () => {
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+
+ element = angular.element('<xos-table config="config" data="data"></xos-table>');
+ compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ };
+
+
+ describe('The xos.helper module', function(){
+ describe('The xos-table component', () => {
+
+ beforeEach(module('xos.helpers'));
+
+ beforeEach(inject(function ($compile, $rootScope, $filter) {
+ compile = $compile;
+ rootScope = $rootScope;
+ filter = $filter;
+ }));
+
+ it('should throw an error if no config is specified', () => {
+ function errorFunctionWrapper(){
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a configuration via the "config" attribute'));
+ });
+
+ it('should throw an error if no config columns are specified', () => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = rootScope.$new();
+ scope.config = 'green';
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a columns list in the configuration'));
+ });
+
+ describe('when basically configured', function() {
+
+ beforeEach(inject(function ($compile, $rootScope) {
+
+ scope = $rootScope.$new();
+
+ scope.config = {
+ columns: [
+ {
+ label: 'Label 1',
+ prop: 'label-1'
+ },
+ {
+ label: 'Label 2',
+ prop: 'label-2'
+ }
+ ]
+ };
+
+ scope.data = [
+ {
+ 'label-1': 'Sample 1.1',
+ 'label-2': 'Sample 1.2'
+ },
+ {
+ 'label-1': 'Sample 2.1',
+ 'label-2': 'Sample 2.2'
+ }
+ ]
+
+ element = angular.element('<xos-table config="config" data="data"></xos-table>');
+ $compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should contain 2 columns', function() {
+ const th = element[0].getElementsByTagName('th');
+ expect(th.length).toEqual(2);
+ expect(isolatedScope.columns.length).toEqual(2);
+ });
+
+ it('should contain 3 rows', function() {
+ const tr = element[0].getElementsByTagName('tr');
+ expect(tr.length).toEqual(3);
+ });
+
+ it('should render labels', () => {
+ let label1 = $(element).find('thead tr th')[0]
+ let label2 = $(element).find('thead tr th')[1]
+ expect($(label1).text().trim()).toEqual('Label 1');
+ expect($(label2).text().trim()).toEqual('Label 2');
+ });
+
+ describe('when no data are provided', () => {
+ beforeEach(() => {
+ isolatedScope.data = [];
+ scope.$digest();
+ });
+ it('should render an alert', () => {
+ let alert = element[0].getElementsByClassName('alert');
+ let table = element[0].getElementsByTagName('table');
+ expect(alert.length).toEqual(1);
+ expect(table.length).toEqual(1);
+ });
+ });
+
+ describe('when a field type is provided', () => {
+ describe('and is boolean', () => {
+ beforeEach(() => {
+ scope.config = {
+ columns: [
+ {
+ label: 'Label 1',
+ prop: 'label-1',
+ type: 'boolean'
+ },
+ {
+ label: 'Label 2',
+ prop: 'label-2',
+ type: 'boolean'
+ }
+ ]
+ };
+ scope.data = [
+ {
+ 'label-1': true,
+ 'label-2': 1
+ },
+ {
+ 'label-1': false,
+ 'label-2': 0
+ }
+ ];
+ compileElement();
+ });
+
+ it('should render an incon', () => {
+ let td1 = $(element).find('tbody tr:first-child td')[0];
+ let td2 = $(element).find('tbody tr:last-child td')[0];
+ expect($(td1).find('i')).toHaveClass('glyphicon-ok');
+ expect($(td2).find('i')).toHaveClass('glyphicon-remove');
+ });
+
+ describe('with field filters', () => {
+ beforeEach(() => {
+ scope.config.filter = 'field';
+ compileElement();
+ });
+
+ it('should render a dropdown for filtering', () => {
+ let td1 = $(element).find('table tbody tr td')[0];
+ expect(td1).toContainElement('select');
+ expect(td1).not.toContainElement('input');
+ });
+
+ it('should correctly filter results', () => {
+ isolatedScope.query = {
+ 'label-1': false
+ };
+ scope.$digest();
+ expect(isolatedScope.query['label-1']).toBeFalsy();
+ const tr = $(element).find('tbody:last-child > tr');
+ const icon = $(tr[0]).find('td i');
+ expect(tr.length).toEqual(1);
+ expect(icon).toHaveClass('glyphicon-remove');
+ });
+
+ it('should correctly filter results if the field is in the form of 0|1', () => {
+ isolatedScope.query = {
+ 'label-2': false
+ };
+ scope.$digest();
+ expect(isolatedScope.query['label-1']).toBeFalsy();
+ const tr = $(element).find('tbody:last-child > tr');
+ const icon = $(tr[0]).find('td i');
+ expect(tr.length).toEqual(1);
+ expect(icon).toHaveClass('glyphicon-remove');
+ });
+ });
+ });
+
+ describe('and is date', () => {
+ beforeEach(() => {
+ scope.config = {
+ columns: [
+ {
+ label: 'Label 1',
+ prop: 'label-1',
+ type: 'date'
+ }
+ ]
+ };
+ scope.data = [
+ {
+ 'label-1': '2015-02-17T22:06:38.059000Z'
+ }
+ ];
+ compileElement();
+ });
+
+ it('should render an formatted date', () => {
+ let td1 = $(element).find('tbody tr:first-child td')[0];
+ const expectedDate = filter('date')(scope.data[0]['label-1'], 'H:mm MMM d, yyyy');
+ expect($(td1).text().trim()).toEqual(expectedDate);
+ });
+ });
+
+ describe('and is array', () => {
+ beforeEach(() => {
+ scope.data = [
+ {categories: ['Film', 'Music']}
+ ];
+ scope.config = {
+ filter: 'field',
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'categories',
+ type: 'array'
+ }
+ ]
+ }
+ compileElement();
+ });
+ it('should render a comma separated list', () => {
+ let td1 = $(element).find('tbody:last-child tr:first-child')[0];
+ expect($(td1).text().trim()).toEqual('Film, Music');
+ });
+
+ it('should not render the filter field', () => {
+ let filter = $(element).find('tbody tr td')[0];
+ expect($(filter)).not.toContainElement('input');
+ });
+ });
+
+ describe('and is object', () => {
+ beforeEach(() => {
+ scope.data = [
+ {
+ attributes: {
+ age: 20,
+ height: 50
+ }
+ }
+ ];
+ scope.config = {
+ filter: 'field',
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'attributes',
+ type: 'object'
+ }
+ ]
+ }
+ compileElement();
+ });
+ it('should render a list of key-values', () => {
+ let td = $(element).find('tbody:last-child tr:first-child')[0];
+ let ageLabel = $(td).find('dl dt')[0];
+ let ageValue = $(td).find('dl dd')[0];
+ let heightLabel = $(td).find('dl dt')[1];
+ let heightValue = $(td).find('dl dd')[1];
+ expect($(ageLabel).text().trim()).toEqual('age');
+ expect($(ageValue).text().trim()).toEqual('20');
+ expect($(heightLabel).text().trim()).toEqual('height');
+ expect($(heightValue).text().trim()).toEqual('50');
+ });
+
+ it('should not render the filter field', () => {
+ let filter = $(element).find('tbody tr td')[0];
+ expect($(filter)).not.toContainElement('input');
+ });
+ });
+
+ describe('and is custom', () => {
+
+ let formatterFn = jasmine.createSpy('formatter').and.returnValue('Formatted Content');
+
+ beforeEach(() => {
+ scope.data = [
+ {categories: ['Film', 'Music']}
+ ];
+ scope.config = {
+ filter: 'field',
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'categories',
+ type: 'custom',
+ formatter: formatterFn
+ }
+ ]
+ }
+ compileElement();
+ });
+
+ it('should check for a formatter property', () => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = rootScope.$new();
+ scope.config = {
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'categories',
+ type: 'custom'
+ }
+ ]
+ };
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.'));
+ });
+
+ it('should check that the formatter property is a function', () => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = rootScope.$new();
+ scope.config = {
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'categories',
+ type: 'custom',
+ formatter: 'formatter'
+ }
+ ]
+ };
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosTable] You have provided a custom field type, a formatter function should provided too.'));
+ });
+
+ it('should format data using the formatter property', () => {
+ let td1 = $(element).find('tbody:last-child tr:first-child')[0];
+ expect($(td1).text().trim()).toEqual('Formatted Content');
+ // the custom formatted should receive the entire object, otherwise is not so custom
+ expect(formatterFn).toHaveBeenCalledWith({categories: ['Film', 'Music']});
+ });
+
+ it('should not render the filter field', () => {
+ // displayed value is different from model val, filter would not work
+ let filter = $(element).find('tbody tr td')[0];
+ expect($(filter)).not.toContainElement('input');
+ });
+ });
+
+ describe('and is icon', () => {
+
+ beforeEach(() => {
+ scope.config = {
+ columns: [
+ {
+ label: 'Label 1',
+ prop: 'label-1',
+ type: 'icon',
+ formatter: item => {
+ switch (item['label-1']){
+ case 1:
+ return 'ok';
+ case 2:
+ return 'remove';
+ case 3:
+ return 'plus'
+ }
+ }
+ }
+ ]
+ };
+ scope.data = [
+ {
+ 'label-1': 1
+ },
+ {
+ 'label-1': 2
+ },
+ {
+ 'label-1': 3
+ }
+ ];
+ compileElement();
+ });
+
+ it('should render a custom icon', () => {
+ let td1 = $(element).find('tbody tr:first-child td')[0];
+ let td2 = $(element).find('tbody tr:nth-child(2) td')[0];
+ let td3 = $(element).find('tbody tr:last-child td')[0];
+ expect($(td1).find('i')).toHaveClass('glyphicon-ok');
+ expect($(td2).find('i')).toHaveClass('glyphicon-remove');
+ expect($(td3).find('i')).toHaveClass('glyphicon-plus');
+ });
+ });
+ });
+
+ describe('when a link property is provided', () => {
+ beforeEach(() => {
+ scope.data = [
+ {
+ id: 1}
+ ];
+ scope.config = {
+ columns: [
+ {
+ label: 'Id',
+ prop: 'id',
+ link: (item) => {
+ return `/link/${item.id}`;
+ }
+ }
+ ]
+ }
+ compileElement();
+ });
+
+ it('should check that the link property is a function', () => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = rootScope.$new();
+ scope.config = {
+ columns: [
+ {
+ label: 'Categories',
+ prop: 'categories',
+ link: 'custom'
+ }
+ ]
+ };
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosTable] The link property should be a function.'));
+ });
+
+ it('should render a link with the correct url', () => {
+ let link = $(element).find('tbody tr:first-child td a')[0];
+ expect($(link).attr('href')).toEqual('/link/1');
+ });
+ });
+
+ describe('when actions are passed', () => {
+
+ let cb = jasmine.createSpy('callback')
+
+ beforeEach(() => {
+ isolatedScope.config.actions = [
+ {
+ label: 'delete',
+ icon: 'remove',
+ cb: cb,
+ color: 'red'
+ }
+ ];
+ scope.$digest();
+ });
+
+ it('should have 3 columns', () => {
+ const th = element[0].getElementsByTagName('th');
+ expect(th.length).toEqual(3);
+ expect(isolatedScope.columns.length).toEqual(2);
+ });
+
+ it('when clicking on action should invoke callback', () => {
+ const link = element[0].getElementsByTagName('a')[0];
+ link.click();
+ expect(cb).toHaveBeenCalledWith(scope.data[0]);
+ });
+ });
+
+ describe('when filter is fulltext', () => {
+ beforeEach(() => {
+ isolatedScope.config.filter = 'fulltext';
+ scope.$digest();
+ });
+
+ it('should render a text field', () => {
+ const textField = element[0].getElementsByTagName('input');
+ expect(textField.length).toEqual(1);
+ });
+
+ describe('and a value is enterd', () => {
+ beforeEach(() => {
+ isolatedScope.query = '2.2';
+ scope.$digest();
+ });
+
+ it('should contain 2 rows', function() {
+ const tr = element[0].getElementsByTagName('tr');
+ expect(tr.length).toEqual(2);
+ });
+ });
+ });
+
+ describe('when filter is field', () => {
+ beforeEach(() => {
+ isolatedScope.config.filter = 'field';
+ scope.$digest();
+ });
+
+ it('should render a text field for each column', () => {
+ const textField = element[0].getElementsByTagName('input');
+ expect(textField.length).toEqual(2);
+ });
+
+ describe('and a value is enterd', () => {
+ beforeEach(() => {
+ isolatedScope.query = {'label-1': '2.1'};
+ scope.$digest();
+ });
+
+ it('should contain 3 rows', function() {
+ const tr = element[0].getElementsByTagName('tr');
+ expect(tr.length).toEqual(3);
+ });
+ });
+ });
+
+ describe('when order is true', () => {
+ beforeEach(() => {
+ isolatedScope.config.order = true;
+ scope.$digest();
+ });
+
+ it('should render a arrows beside', () => {
+ const arrows = element[0].getElementsByTagName('i');
+ expect(arrows.length).toEqual(4);
+ });
+
+ describe('and a default ordering is passed', () => {
+
+ beforeEach(() => {
+ scope.config.order = {
+ field: 'label-1',
+ reverse: true
+ };
+ compileElement();
+ });
+
+ it('should orderBy the default order', () => {
+ const tr = $(element).find('tr');
+ expect($(tr[1]).text()).toContain('Sample 2.2');
+ expect($(tr[2]).text()).toContain('Sample 1.1');
+ });
+ });
+
+ describe('and an order is set', () => {
+ beforeEach(() => {
+ isolatedScope.orderBy = 'label-1';
+ isolatedScope.reverse = true;
+ scope.$digest();
+ });
+
+ it('should orderBy', function() {
+ // console.log($(element).find('table'));
+ const tr = $(element).find('tr');
+ expect($(tr[1]).text()).toContain('Sample 2.2');
+ expect($(tr[2]).text()).toContain('Sample 1.1');
+ });
+ });
+ });
+ });
+ });
+ });
+})();
+
diff --git a/spec/ui/validation.test.js b/spec/ui/validation.test.js
new file mode 100644
index 0000000..44d9f96
--- /dev/null
+++ b/spec/ui/validation.test.js
@@ -0,0 +1,86 @@
+/**
+ * © OpenCORD
+ *
+ * Created by teone on 4/15/16.
+ */
+
+(function () {
+ 'use strict';
+
+ let compile, element, scope, rootScope;
+
+ const compileElement = (el) => {
+ element = el;
+
+ if(!scope){
+ scope = rootScope.$new();
+ }
+ if(angular.isUndefined(element)){
+ element = angular.element('<xos-validation field="field" form="form"></xos-validation>');
+ }
+ compile(element)(scope);
+ scope.$digest();
+ }
+
+ describe('The xos.helper module', function(){
+ describe('The xos-validation component', () => {
+
+ beforeEach(module('xos.helpers'));
+
+ describe('when the form has no errors', () => {
+ beforeEach(inject(($compile, $rootScope) => {
+ compile = $compile;
+ scope = $rootScope.$new();
+
+ scope.field = {
+ $error: {}
+ };
+
+ scope.form = {
+ $submitted: true
+ }
+
+ compileElement();
+ }));
+
+ it('should not show an alert by default', () => {
+ expect($(element).find('xos-alert > .alert')[0]).toHaveClass('ng-hide');
+ });
+ });
+
+ let availableErrors = [
+ {
+ type: 'required',
+ message: 'Field required'
+ },
+ {
+ type: 'email',
+ message: 'This is not a valid email'
+ },
+ {
+ type: 'minlength',
+ message: 'Too short'
+ },
+ {
+ type: 'maxlength',
+ message: 'Too long'
+ },
+ {
+ type: 'custom',
+ message: 'Field invalid'
+ },
+ ];
+
+ // use a loop to generate similar test
+ availableErrors.forEach((e, i) => {
+ it(`should show an alert for ${e.type} errors`, () => {
+ scope.field.$error[e.type] = true;
+ compileElement();
+ let alert = $(element).find('xos-alert > .alert')[i];
+ expect(alert).not.toHaveClass('ng-hide');
+ expect(alert).toHaveText(e.message);
+ });
+ });
+ });
+ });
+})();
\ No newline at end of file
diff --git a/spec/ui/xos.helpers.mock.js b/spec/ui/xos.helpers.mock.js
new file mode 100644
index 0000000..bf0f0f5
--- /dev/null
+++ b/spec/ui/xos.helpers.mock.js
@@ -0,0 +1,5 @@
+(function () {
+ 'use strict';
+ angular.module('xos.helpers', ['xos.uiComponents'])
+ .factory('_', $window => $window._ );
+})();
\ No newline at end of file