diff --git a/README.md b/README.md index a8df77f6ab..8bd93762d9 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,67 @@ # Project Title -One Paragraph of project description goes here +Salix is an Enterprise resource planning (ERP) integrated management of core business processes, in real-time and mediated by software and technology developed with the stack listed below. + +Salix is also the scientific name of a beautifull tree! :) ### Prerequisites -For testing purposes you will need to install globally the following items: -npm install -g karma -npm install -g karma-cli - -## Getting Started // ### Installing - -Pull from repo. - -install nodejs v6 or above. +You will need to install globally the following items: +$ npm install -g karma +$ npm install -g karma-cli +$ npm install -g gulp +$ npm install -g webpack +$ npm install -g nodemon install nginx globally. -Ask a senior dev for the datasources.development.json files required to run the project. +## Getting Started // ### Installing + +Pull from repository. + +install nodejs v6. + +Ask a senior developer for the datasources.development.json files required to run the project. on root run: -npm install -gulp install +$ npm install +$ gulp install lauching nginx: -./dev.sh +$ ./dev.sh launching frontend: -gulp client +$ gulp client +or start nginx before client on sequence +$ gulp clientDev launching backend: -gulp services +$ gulp services +or start the local database before services on sequence +$ gulp serivcesDev + +Manually reset local fixtures: +$ gulp docker + +to check docker images and containers status: +$ docker images +$ docker ps -a ## Running the tests for client-side unit tests run from project's root: -karma start +$ karma start for server-side unit tests run from project's root: -npm run testWatch or test for single run +$ npm run test ### Break down into end to end tests -on root run: +Run local database plus e2e paths: +$ gulp e2e -gulp docker - -wait 10 secs for db to be ready - -npm run e2e +Just the e2e paths as the fixtures are untainted: +$ npm run e2e ## Built With diff --git a/client/client/src/greuge-create/greuge-create.html b/client/client/src/greuge-create/greuge-create.html index a3e5825e69..2e8ed24545 100644 --- a/client/client/src/greuge-create/greuge-create.html +++ b/client/client/src/greuge-create/greuge-create.html @@ -5,6 +5,7 @@ form="form" save="post"> </vn-watcher> + <form pad-medium name="form" ng-submit="$ctrl.onSubmit()"> <vn-card> <vn-vertical pad-medium> diff --git a/client/client/src/greuge-create/greuge-create.spec.js b/client/client/src/greuge-create/greuge-create.spec.js new file mode 100644 index 0000000000..1cc408088f --- /dev/null +++ b/client/client/src/greuge-create/greuge-create.spec.js @@ -0,0 +1,39 @@ +import './greuge-create.js'; + +describe('Client', () => { + describe('Component vnClientGreugeCreate', () => { + let $componentController; + let $scope; + let $state; + let controller; + + beforeEach(() => { + angular.mock.module('client'); + }); + + beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => { + $componentController = _$componentController_; + $scope = $rootScope.$new(); + $state = _$state_; + $scope.watcher = { + submit: () => { + return { + then: callback => { + callback(); + } + }; + } + }; + controller = $componentController('vnClientGreugeCreate', {$scope: $scope}); + })); + + describe('onSubmit()', () => { + it('should call the function go() on $state to go to the greuges list', () => { + spyOn($state, 'go'); + controller.onSubmit(); + + expect(controller.$state.go).toHaveBeenCalledWith('clientCard.greuge.list'); + }); + }); + }); +}); diff --git a/client/core/src/directives/validation.js b/client/core/src/directives/validation.js index 10669e7388..09c4a9f8ed 100644 --- a/client/core/src/directives/validation.js +++ b/client/core/src/directives/validation.js @@ -29,7 +29,6 @@ export function directive(interpolate, compile, $window) { throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`); let validations = entity.validations[fieldName]; - if (!validations || validations.length == 0) return; diff --git a/client/core/src/lib/validator.js b/client/core/src/lib/validator.js index 704961318e..9f8d6d53f2 100644 --- a/client/core/src/lib/validator.js +++ b/client/core/src/lib/validator.js @@ -2,7 +2,7 @@ import {validator} from 'vendor'; export const validators = { presence: value => { - if (validator.isEmpty(value)) + if (validator.isEmpty(value ? String(value) : '')) throw new Error(`Value can't be empty`); }, absence: value => { @@ -74,7 +74,7 @@ export function validate(value, conf) { try { checkNull(value, conf); - if (validator && value != null) + if (validator) // && value != null ?? validator(value, conf); } catch (e) { let message = conf.message ? conf.message : e.message; diff --git a/e2e/helpers/nightmare.js b/e2e/helpers/nightmare.js index 777d178625..8bc84526dc 100644 --- a/e2e/helpers/nightmare.js +++ b/e2e/helpers/nightmare.js @@ -4,11 +4,11 @@ import Nightmare from 'nightmare'; export default function createNightmare(width = 1280, height = 720) { const nightmare = new Nightmare({show: true, typeInterval: 10, x: 0, y: 0}).viewport(width, height); - nightmare.on('page', function(type, message, error) { + nightmare.on('page', (type, message, error) => { fail(error); }); - nightmare.on('console', function(type, message) { + nightmare.on('console', (type, message) => { if (type === 'error') { fail(message); } diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 514abb232d..ea076f76af 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -128,11 +128,15 @@ export default { greuge: { greugeButton: `${components.vnMenuItem}[ui-sref="clientCard.greuge.list"]`, addGreugeFloatButton: `${components.vnFloatButton}`, - amountInput: `${components.vnTextfield}[name="Amount"]`, - descriptionInput: `${components.vnTextfield}[name="Description"]`, + amountInput: `${components.vnTextfield}[name="amount"]`, + descriptionInput: `${components.vnTextfield}[name="description"]`, typeInput: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] > vn-vertical > ${components.vnTextfield}`, typeSecondOption: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto:nth-child(2) > ul > li`, - saveButton: `${components.vnSubmit}` - // firstGreugeText: '' + saveButton: `${components.vnSubmit}`, + firstGreugeText: 'body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > ui-view > vn-client-greuge-list > vn-card > div > vn-vertical > vn-one > vn-horizontal' + }, + mandate: { + mandateButton: `${components.vnMenuItem}[ui-sref="clientCard.mandate"]`, + firstMandateText: 'body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-client-mandate > vn-card > div > vn-vertical > vn-one > vn-horizontal' } }; diff --git a/e2e/paths/01_create_client_path.spec.js b/e2e/paths/01_create_client_path.spec.js index 75828ba4ba..b235dc8183 100644 --- a/e2e/paths/01_create_client_path.spec.js +++ b/e2e/paths/01_create_client_path.spec.js @@ -86,7 +86,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toEqual('No changes to save'); + expect(result).toEqual('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -100,7 +100,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -115,7 +115,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -130,7 +130,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -145,7 +145,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -175,7 +175,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -190,7 +190,7 @@ describe('create client path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); diff --git a/e2e/paths/05_edit_addresses.spec.js b/e2e/paths/05_edit_addresses.spec.js index 63966d8bab..b87ba7fd4c 100644 --- a/e2e/paths/05_edit_addresses.spec.js +++ b/e2e/paths/05_edit_addresses.spec.js @@ -103,7 +103,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -117,7 +117,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -132,7 +132,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -147,7 +147,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -162,7 +162,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -178,7 +178,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -193,7 +193,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -207,7 +207,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -222,7 +222,7 @@ describe('Edit addresses path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); diff --git a/e2e/paths/09_add_greuge.spec.js b/e2e/paths/09_add_greuge.spec.js index 0846ab5aa2..4dd6990cf0 100644 --- a/e2e/paths/09_add_greuge.spec.js +++ b/e2e/paths/09_add_greuge.spec.js @@ -102,7 +102,7 @@ describe('Add greuge path', () => { .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -111,25 +111,25 @@ describe('Add greuge path', () => { it(`should receive an error if all fields are empty but date and amount on submit`, done => { nightmare .type(selectors.greuge.amountInput, 999) - .click(selectors.credit.saveButton) + .click(selectors.greuge.saveButton) .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); }); - it(`should receive an error if all fields are empty but date and amount on submit`, done => { + it(`should receive an error if all fields are empty but date and description on submit`, done => { nightmare .clearInput(selectors.greuge.amountInput) - .type(selectors.greuge.descriptionInput, 'Bat-flying suite with anti-APCR rounds') - .click(selectors.credit.saveButton) + .type(selectors.greuge.descriptionInput, 'new armor for Batman!') + .click(selectors.greuge.saveButton) .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); done(); }) .catch(catchErrors(done)); @@ -140,11 +140,39 @@ describe('Add greuge path', () => { .clearInput(selectors.greuge.descriptionInput) .waitToClick(selectors.greuge.typeInput) .waitToClick(selectors.greuge.typeSecondOption) - .click(selectors.credit.saveButton) + .click(selectors.greuge.saveButton) .wait(selectors.globalItems.snackbarIsActive) .getInnerText(selectors.globalItems.snackbarIsActive) .then(result => { - expect(result).toContain('Error'); + expect(result).toContain('Some fields are invalid'); + done(); + }) + .catch(catchErrors(done)); + }); + + it(`should create a new greuge with all its data`, done => { + nightmare + .type(selectors.greuge.amountInput, 999) + .type(selectors.greuge.descriptionInput, 'new armor for Batman!') + .click(selectors.greuge.saveButton) + .wait(selectors.globalItems.snackbarIsActive) + .getInnerText(selectors.globalItems.snackbarIsActive) + .then(result => { + expect(result).toContain('Data saved!'); + done(); + }) + .catch(catchErrors(done)); + }); + + it('should confirm the greuge was added to the list', done => { + nightmare + .waitForSnackbarReset() + .wait(selectors.greuge.firstGreugeText) + .getInnerText(selectors.greuge.firstGreugeText) + .then(value => { + expect(value).toContain(999); + expect(value).toContain('new armor for Batman!'); + expect(value).toContain('Diff'); done(); }) .catch(catchErrors(done)); diff --git a/e2e/paths/10_mandate.spec.js b/e2e/paths/10_mandate.spec.js new file mode 100644 index 0000000000..ab7847ee77 --- /dev/null +++ b/e2e/paths/10_mandate.spec.js @@ -0,0 +1,99 @@ +import config from '../helpers/config.js'; +import createNightmare from '../helpers/nightmare'; +import selectors from '../helpers/selectors.js'; +import {catchErrors} from '../../services/utils/jasmineHelpers'; +const nightmare = createNightmare(); +const moduleAccessViewHashURL = '#!/'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; + +describe('mandate path', () => { + describe('warm up', () => { + it('should warm up login and fixtures', done => { + nightmare + .login() + .waitForURL(moduleAccessViewHashURL) + .waitToClick(selectors.globalItems.logOutButton) + .then(() => { + done(); + }) + .catch(catchErrors(done)); + }); + }); + + it('should log in', done => { + nightmare + .login() + .waitForURL(moduleAccessViewHashURL) + .url() + .then(url => { + expect(url).toEqual(config.url + moduleAccessViewHashURL); + done(); + }) + .catch(catchErrors(done)); + }); + + it('should make sure the language is English', done => { + nightmare + .changeLanguageToEnglish() + .then(() => { + done(); + }) + .catch(catchErrors(done)); + }); + + it('should click on the Clients button of the top bar menu', done => { + nightmare + .waitToClick(selectors.globalItems.applicationsMenuButton) + .wait(selectors.globalItems.applicationsMenuVisible) + .waitToClick(selectors.globalItems.clientsButton) + .wait(selectors.clientsIndex.createClientButton) + .url() + .then(url => { + expect(url).toEqual(config.url + '#!/clients'); + done(); + }) + .catch(catchErrors(done)); + }); + + it('should search for the user Petter Parker', done => { + nightmare + .wait(selectors.clientsIndex.searchResult) + .type(selectors.clientsIndex.searchClientInput, 'Petter Parker') + .click(selectors.clientsIndex.searchButton) + .waitForNumberOfElements(selectors.clientsIndex.searchResult, 1) + .countSearchResults(selectors.clientsIndex.searchResult) + .then(result => { + expect(result).toEqual(1); + done(); + }) + .catch(catchErrors(done)); + }); + + it(`should click on the search result to access to the client's mandate`, done => { + nightmare + .waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter') + .waitToClick(selectors.clientsIndex.searchResult) + .waitToClick(selectors.mandate.mandateButton) + .waitForURL('mandate') + .url() + .then(url => { + expect(url).toContain('mandate'); + done(); + }) + .catch(catchErrors(done)); + }); + + it('should confirm the client has a mandate of the CORE type', done => { + nightmare + .wait(selectors.mandate.firstMandateText) + .getInnerText(selectors.mandate.firstMandateText) + .then(value => { + expect(value).toContain('1'); + expect(value).toContain('WAY'); + expect(value).toContain('CORE'); + done(); + }) + .catch(catchErrors(done)); + }); +}); diff --git a/gulpfile.js b/gulpfile.js index 13fa1709e8..656b81308c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -40,7 +40,7 @@ gulp.task('client', ['clean'], function() { gulp.task('nginxRestart', callback => { let isWindows = /^win/.test(process.platform); - let command = isWindows ? './dev.cmd' : './dev.sh'; + let command = isWindows ? '.\\dev.cmd' : './dev.sh'; exec(command, (err, stdout, stderr) => { console.log(stdout); callback(err); diff --git a/package.json b/package.json index 536a324b67..cd2a8a2d74 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,7 @@ "build": "webpack --progress --colors", "dev": "webpack-dev-server --progress --colors", "lint": "eslint ./ --cache --ignore-pattern .gitignore", - "test": "node services_tests", - "testWatcher": "nodemon -q services_tests.js -w services", + "test": "nodemon -q services_tests.js -w services", "e2e": "node e2e_tests" } } diff --git a/services/client/common/models/client.js b/services/client/common/models/client.js index 0340042d32..85a24496e5 100644 --- a/services/client/common/models/client.js +++ b/services/client/common/models/client.js @@ -18,15 +18,9 @@ module.exports = function(Self) { // Validations - Self.validatesUniquenessOf('name', { - message: 'El nombre debe ser único' - }); Self.validatesUniquenessOf('fi', { message: 'El NIF/CIF debe ser único' }); - Self.validatesPresenceOf('socialName', { - message: 'Debe especificarse la razón social' - }); Self.validatesUniquenessOf('socialName', { message: 'La razón social debe ser única' }); diff --git a/services/client/common/models/client.json b/services/client/common/models/client.json index 223d6ef8f7..e2600e0b45 100644 --- a/services/client/common/models/client.json +++ b/services/client/common/models/client.json @@ -22,7 +22,8 @@ "description": "Fiscal indentifier" }, "socialName": { - "type": "string" + "type": "string", + "required": true }, "contact": { "type": "string" diff --git a/services/client/common/models/greuge.js b/services/client/common/models/greuge.js index 8364011d5a..9cf127d419 100644 --- a/services/client/common/models/greuge.js +++ b/services/client/common/models/greuge.js @@ -2,11 +2,8 @@ module.exports = function(Self) { require('../methods/greuge/filter.js')(Self); require('../methods/greuge/totalGreuge.js')(Self); - Self.validatesPresenceOf('description', 'amount', 'greugeTypeFk', { - message: 'Importe es un campo obligatorio' - }); Self.validatesLengthOf('description', { max: 45, - message: 'description es un campo max 45' + message: 'La description debe tener maximo 45 caracteres' }); }; diff --git a/services/client/common/models/greuge.json b/services/client/common/models/greuge.json index 37c2c000a3..c7a4073410 100644 --- a/services/client/common/models/greuge.json +++ b/services/client/common/models/greuge.json @@ -14,10 +14,12 @@ "description": "Identifier" }, "description": { - "type": "String" + "type": "String", + "required": true }, "amount": { - "type": "Number" + "type": "Number", + "required": true }, "shipped": { "type": "date" @@ -35,7 +37,8 @@ "greugeType": { "type": "belongsTo", "model": "GreugeType", - "foreignKey": "greugeTypeFk" + "foreignKey": "greugeTypeFk", + "required": true } } } \ No newline at end of file diff --git a/services/db/localDB09Inserts.sql b/services/db/localDB09Inserts.sql index a59dcfa780..66046946c8 100644 --- a/services/db/localDB09Inserts.sql +++ b/services/db/localDB09Inserts.sql @@ -525,5 +525,5 @@ INSERT INTO `vn`.`mandateType`(`id`, `name`) INSERT INTO `vn`.`mandate`(`id`, `clientFk`, `companyFk`, `code`, `created`, `mandateTypeFk`) VALUES - (1, 1, 442, '1-1', CURDATE(), 2); + (1, 2, 442, '1-1', CURDATE(), 2); diff --git a/services/loopback/server/boot/validations.js b/services/loopback/server/boot/validations.js index 06a8a8c0b5..fe5e5d3654 100644 --- a/services/loopback/server/boot/validations.js +++ b/services/loopback/server/boot/validations.js @@ -36,7 +36,7 @@ module.exports = function(app) { for (let validation of validations[fieldName]) { let options = validation.options; - if ((options && options.async) || + if ((options && options.async) || (validation.validation == 'custom' && !validation.isExportable)) continue; diff --git a/services_tests.js b/services_tests.js index 9a282490fc..3aed201163 100644 --- a/services_tests.js +++ b/services_tests.js @@ -17,13 +17,12 @@ var jasmine = new Jasmine(); var SpecReporter = require('jasmine-spec-reporter').SpecReporter; jasmine.loadConfig({ - spec_dir: 'services/', + spec_dir: 'services', spec_files: [ - '**/specs/*[sS]pec.js' + 'client/common/**/*[sS]pec.js' ], helpers: [ - // to implement - // '/api/utils/jasmineHelpers.js' + '/services/utils/jasmineHelpers.js' ] }); @@ -36,6 +35,5 @@ jasmine.addReporter(new SpecReporter({ } })); -exports.start = () => { - jasmine.execute(); -}; +jasmine.execute(); +