├── .editorconfig ├── .gitignore ├── .storybook ├── addons.js ├── config.js ├── tsconfig.json └── typings.d.ts ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── reports │ ├── html │ │ └── cucumber_reporter.html │ └── json │ │ └── cucumber_report.json ├── src │ ├── config │ │ └── chai-imports.ts │ ├── features │ │ ├── F01_Login.feature │ │ ├── F02_Motorista.feature │ │ └── F03_Logout.feature │ ├── step-definitions │ │ ├── home.spec.ts │ │ ├── login.spec.ts │ │ └── pages │ │ │ ├── dialog.po.ts │ │ │ ├── home.po.ts │ │ │ └── login.po.ts │ └── support │ │ └── hooks.ts └── tsconfig.json ├── karma.conf.js ├── ngsw-config.json ├── package-lock.json ├── package.json ├── print-ng-e2e.png ├── print-ng-test.png ├── src ├── app │ ├── app-material.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── data │ │ ├── base │ │ │ └── mapper.ts │ │ ├── data.module.ts │ │ └── repository │ │ │ ├── motorista │ │ │ ├── motorista-mapper.spec.ts │ │ │ ├── motorista-mapper.ts │ │ │ ├── motorista-repository.service.spec.ts │ │ │ └── motorista-repository.service.ts │ │ │ └── usuario │ │ │ ├── iusuario-request-entity.spec.ts │ │ │ ├── iusuario-request-entity.ts │ │ │ ├── iusuario-response-entity.spec.ts │ │ │ ├── iusuario-response-entity.ts │ │ │ ├── usuario-mapper.spec.ts │ │ │ ├── usuario-mapper.ts │ │ │ ├── usuario-repository.spec.ts │ │ │ └── usuario-repository.ts │ ├── domain │ │ ├── domain.module.ts │ │ ├── entities │ │ │ ├── addresses-entity.spec.ts │ │ │ ├── addresses-entity.ts │ │ │ ├── base │ │ │ │ ├── domain-entity.spec.ts │ │ │ │ └── domain-entity.ts │ │ │ ├── documents-entity.spec.ts │ │ │ ├── documents-entity.ts │ │ │ ├── driver-entity.spec.ts │ │ │ ├── driver-entity.ts │ │ │ ├── user-entity.spec.ts │ │ │ └── user-entity.ts │ │ ├── interfaces │ │ │ ├── controllers │ │ │ │ ├── imotorista-controller.ts │ │ │ │ └── iusuario-controller.ts │ │ │ ├── entity │ │ │ │ └── domain-entity.ts │ │ │ ├── message │ │ │ │ └── ivalidator-message.ts │ │ │ ├── repository │ │ │ │ ├── imotorista-repository.ts │ │ │ │ └── iusuario-repository.ts │ │ │ ├── usecases │ │ │ │ ├── imotorista-usecase.ts │ │ │ │ └── iusuario-use-case.ts │ │ │ └── validations │ │ │ │ ├── imotorista-validator.ts │ │ │ │ └── iusuario-validator.ts │ │ ├── message │ │ │ ├── validator-message.service.spec.ts │ │ │ └── validator-message.service.ts │ │ ├── usecases │ │ │ ├── motorista │ │ │ │ ├── motorista-usecase.service.spec.ts │ │ │ │ └── motorista-usecase.service.ts │ │ │ └── usuario │ │ │ │ ├── usuario-use-case.spec.ts │ │ │ │ └── usuario-use-case.ts │ │ └── validations │ │ │ ├── motorista │ │ │ ├── motorista-validator.service.spec.ts │ │ │ └── motorista-validator.service.ts │ │ │ └── usuario │ │ │ ├── usuario-validator.service.spec.ts │ │ │ └── usuario-validator.service.ts │ ├── infra │ │ ├── auth │ │ │ ├── auth.guard.spec.ts │ │ │ ├── auth.guard.ts │ │ │ ├── auth.service.spec.ts │ │ │ └── auth.service.ts │ │ ├── http │ │ │ ├── http-interceptor.service.spec.ts │ │ │ └── http-interceptor.service.ts │ │ ├── infra.module.ts │ │ └── translations │ │ │ ├── i18n.service.spec.ts │ │ │ └── i18n.service.ts │ └── presentation │ │ ├── controllers │ │ ├── motorista │ │ │ ├── motorista-controller.service.spec.ts │ │ │ └── motorista-controller.service.ts │ │ └── usuario │ │ │ ├── usuario-controller.service.spec.ts │ │ │ └── usuario-controller.service.ts │ │ ├── presentation.module.ts │ │ └── view │ │ ├── base │ │ ├── base.component.html │ │ ├── base.component.scss │ │ ├── base.component.spec.ts │ │ ├── base.component.ts │ │ └── base.module.ts │ │ ├── pages │ │ ├── home │ │ │ ├── home-routing.module.ts │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ └── home.module.ts │ │ ├── login │ │ │ ├── login-routing.module.ts │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ ├── login.component.spec.ts │ │ │ ├── login.component.ts │ │ │ └── login.module.ts │ │ ├── pages-routing.module.ts │ │ ├── pages.module.ts │ │ └── route.service.ts │ │ ├── shared │ │ ├── components │ │ │ └── input │ │ │ │ ├── input.component.html │ │ │ │ ├── input.component.scss │ │ │ │ ├── input.component.spec.ts │ │ │ │ └── input.component.ts │ │ ├── dialogs │ │ │ └── dialog-cadastro │ │ │ │ ├── dialog-cadastro.component.html │ │ │ │ ├── dialog-cadastro.component.scss │ │ │ │ ├── dialog-cadastro.component.spec.ts │ │ │ │ └── dialog-cadastro.component.ts │ │ ├── notification │ │ │ ├── notification.component.html │ │ │ ├── notification.component.scss │ │ │ ├── notification.component.spec.ts │ │ │ ├── notification.component.ts │ │ │ ├── notification.service.spec.ts │ │ │ └── notification.service.ts │ │ └── shared.module.ts │ │ └── view.module.ts ├── assets │ ├── .gitkeep │ ├── i18n │ │ └── pt-BR.json │ ├── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png │ └── images │ │ └── img-avatar.jpg ├── backend │ └── server.js ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── manifest.webmanifest ├── polyfills.ts ├── stories │ └── index.stories.ts ├── styles.scss ├── test.ts └── typings.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | import '@storybook/addon-notes/register'; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/angular'; 2 | 3 | // automatically import all files ending in *.stories.ts 4 | const req = require.context('../src/stories', true, /\.stories\.ts$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.app.json", 3 | "compilerOptions": { 4 | "types": [ 5 | "node" 6 | ] 7 | }, 8 | "exclude": [ 9 | "../src/test.ts", 10 | "../src/**/*.spec.ts", 11 | "../projects/**/*.spec.ts" 12 | ], 13 | "include": [ 14 | "../src/**/*", 15 | "../projects/**/*" 16 | ], 17 | "files": [ 18 | "./typings.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.storybook/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Clean Architecture 2 | 3 | Um modelo inicial completo: exemplo de estrutura de aplicativo sob medida para escabilidade e boas praticas de desenvolvimento com Clean Code, SOLID, Clean Architecture e Orientação a Objetose código para cada coisa comum necessária em projetos corporativos, como testes de unidade, roteamento, autenticação, extensões de serviço HTTPS, suporte a i18n com alteração dinâmica de idioma e detecção automática de idioma do usuário 4 | 5 | ## Começando 6 | 7 | Baixe o repositório: 8 | 9 | ```bash 10 | git clone https://github.com/leandro-mancini/angular-clean-architecture.git 11 | ``` 12 | 13 | Dados de autenticação do usuário: 14 | 15 | ***Usuário***: test 16 | 17 | ***Password*** 123 18 | 19 | ### Angular aplicativo web 20 | 21 | 1. Instale as dependências: 22 | 23 | ```bash 24 | npm install 25 | ``` 26 | 27 | 2. Inicie o servidor de desenvolvimento e abra localhost:4200 em seu navegador: 28 | 29 | ```bash 30 | npm start 31 | ``` 32 | 33 | 3. Instalar o JSON Server: 34 | 35 | ```bash 36 | npm install -g json-server 37 | ``` 38 | 39 | 4. Abra um novo terminal e inicie o servidor JSON: 40 | 41 | ```bash 42 | npm run server 43 | ``` 44 | 45 | ### Estrutura do projeto 46 | 47 | ```` 48 | | - app 49 | | | - core 50 | | | | - domain 51 | | | | | - [+] entity 52 | | | | - interfaces 53 | | | | | - [+] controllers 54 | | | | | - [+] entity 55 | | | | | - [+] message 56 | | | | | - [+] repository 57 | | | | | - [+] usecases 58 | | | | | - [+] validations 59 | | | | - [+] message 60 | | | | - [+] usecases 61 | | | | - core.module.ts 62 | | | - data 63 | | | | - [+] repository 64 | | | | - data.module.ts 65 | | | - infra 66 | | | | - [+] auth 67 | | | | - [+] http 68 | | | | - [+] translations 69 | | | | - infra.module.ts 70 | | | - presentation 71 | | | | - [+] base 72 | | | | - [+] controllers 73 | | | | - [+] pages 74 | | | | - [+] shared 75 | | | | - presentation.module.ts 76 | ```` 77 | 78 | ### Principais tarefas 79 | 80 | A automação de tarefas é baseada em scripts do [NPM scripts](https://docs.npmjs.com/misc/scripts). 81 | 82 | Tarefas | Descrição 83 | ----------------------------- |--------------------------------------------------------------------------------------- 84 | npm start | Execute o servidor de desenvolvimento em `http://localhost:4200/` 85 | npm run test | Execute testes unitários via [Karma](https://karma-runner.github.io) no modo de observação 86 | npm run e2e | Executar testes e2e usando [Protractor](http://www.protractortest.org) 87 | npm run lint | Código Lint 88 | npm run server | Executar servidor de desenvolvimento APIs 89 | npm run translations:extract | Extrair strings do código e modelos para `src/assets/i18n/template.json` 90 | 91 | ### O que esta no pacote 92 | 93 | O modelo do aplicativo é baseado em [HTML5](http://whatwg.org/html), [TypeScript](http://www.typescriptlang.org) e [Sass](http://sass-lang.com). 94 | Os arquivos de tradução usam o formato [JSON](http://www.json.org) comum . 95 | 96 | ### Ferramentas 97 | 98 | Os processos de desenvolvimento, construção e qualidade são baseados em scripts [angular-cli](https://github.com/angular/angular-cli) e [NPM scripts](https://docs.npmjs.com/misc/scripts), que incluem: 99 | 100 | - Processo otimizado de compilação e empacotamento com o [Webpack](https://webpack.github.io) 101 | - CSS entre navegadores com [autoprefixer](https://github.com/postcss/autoprefixer) e [browserslist](https://github.com/ai/browserslist) 102 | - Testes de unidade usando [Jasmine](http://jasmine.github.io) e [Karma](https://karma-runner.github.io) 103 | - Testes de ponta a ponta usando [Protractor](https://github.com/angular/protractor) 104 | - Análise de código estático: [TSLint](https://github.com/palantir/tslint), [Codelyzer](https://github.com/mgechev/codelyzer), [Stylelint](http://stylelint.io) e [HTMLHint](http://htmlhint.com/) 105 | 106 | ### Bibliotecas 107 | 108 | - [Angular](https://angular.io) 109 | - [Material Angular](https://material.angular.io) 110 | - [Bootstrap 4](https://getbootstrap.com) 111 | - [RxJS](http://reactivex.io/rxjs) 112 | - [ngx-translate](https://github.com/ngx-translate/core) 113 | - [Lodash](https://lodash.com) 114 | - [Moment](https://momentjs.com) 115 | - [AutoMapper](https://github.com/loedeman/AutoMapper) 116 | - [Fluent validator](https://github.com/VeritasSoftware/ts.validator) 117 | - [Jasmine](https://jasmine.github.io) 118 | - [chai](https://www.chaijs.com) 119 | - [Cucumber](https://cucumber.io) 120 | - [Storybook](https://storybook.js.org) 121 | 122 | ## Executando teste de unidade 123 | 124 | 1. Abra um novo terminal e inicie o servidor JSON: 125 | 126 | ```bash 127 | npm run server 128 | ``` 129 | 130 | 2. Inicie o servidor de test: 131 | 132 | ```bash 133 | npm run test 134 | ``` 135 | 136 | 3. Vá para a pasta do projeto web `./coverage`. 137 | 138 | 4. Execute o arquivo `index.html` para poder visualizar o code covarage: 139 | 140 | 141 | 142 |  143 | 144 | ## Executando teste end-to-end 145 | 146 | 1. Abra um novo terminal e inicie o servidor JSON: 147 | 148 | ```bash 149 | npm run server 150 | ``` 151 | 152 | 2. Inicie o servidor de test: 153 | 154 | ```bash 155 | npm run e2e 156 | ``` 157 | 158 | 3. Após ter executado todo o teste vá para a pasta do projeto web `./e2e/reports/html`. 159 | 160 | 4. Execute o arquivo `cucumber_reporter.html` para poder visualizar um dashboard do cucumber dos steps de cada teste. 161 | 162 |  163 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-clean-architecture": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": false, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets", 29 | "src/manifest.webmanifest" 30 | ], 31 | "styles": [ 32 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 33 | "src/styles.scss" 34 | ], 35 | "scripts": [] 36 | }, 37 | "configurations": { 38 | "production": { 39 | "fileReplacements": [ 40 | { 41 | "replace": "src/environments/environment.ts", 42 | "with": "src/environments/environment.prod.ts" 43 | } 44 | ], 45 | "optimization": true, 46 | "outputHashing": "all", 47 | "sourceMap": false, 48 | "extractCss": true, 49 | "namedChunks": false, 50 | "aot": true, 51 | "extractLicenses": true, 52 | "vendorChunk": false, 53 | "buildOptimizer": true, 54 | "budgets": [ 55 | { 56 | "type": "initial", 57 | "maximumWarning": "2mb", 58 | "maximumError": "5mb" 59 | } 60 | ], 61 | "serviceWorker": true, 62 | "ngswConfigPath": "ngsw-config.json" 63 | } 64 | } 65 | }, 66 | "serve": { 67 | "builder": "@angular-devkit/build-angular:dev-server", 68 | "options": { 69 | "browserTarget": "angular-clean-architecture:build" 70 | }, 71 | "configurations": { 72 | "production": { 73 | "browserTarget": "angular-clean-architecture:build:production" 74 | } 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "angular-clean-architecture:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "karmaConfig": "karma.conf.js", 90 | "codeCoverage": true, 91 | "assets": [ 92 | "src/favicon.ico", 93 | "src/assets", 94 | "src/manifest.webmanifest" 95 | ], 96 | "styles": [ 97 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 98 | "src/styles.scss" 99 | ], 100 | "scripts": [] 101 | } 102 | }, 103 | "lint": { 104 | "builder": "@angular-devkit/build-angular:tslint", 105 | "options": { 106 | "tsConfig": [ 107 | "tsconfig.app.json", 108 | "tsconfig.spec.json", 109 | "e2e/tsconfig.json" 110 | ], 111 | "exclude": [ 112 | "**/node_modules/**" 113 | ] 114 | } 115 | }, 116 | "e2e": { 117 | "builder": "@angular-devkit/build-angular:protractor", 118 | "options": { 119 | "protractorConfig": "e2e/protractor.conf.js", 120 | "devServerTarget": "angular-clean-architecture:serve" 121 | }, 122 | "configurations": { 123 | "production": { 124 | "devServerTarget": "angular-clean-architecture:serve:production" 125 | } 126 | } 127 | } 128 | } 129 | } 130 | }, 131 | "defaultProject": "angular-clean-architecture" 132 | } 133 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | let fs = require("fs"); 6 | let mkdirp = require("mkdirp"); 7 | let path = require("path"); 8 | let reporter = require('cucumber-html-reporter'); 9 | 10 | const jsonReports = path.join(process.cwd(), "e2e/reports/json"); 11 | const htmlReports = path.join(process.cwd(), "e2e/reports/html"); 12 | const targetJson = jsonReports + "/cucumber_report.json"; 13 | 14 | let options = { 15 | theme: 'bootstrap', 16 | jsonFile: targetJson, 17 | output: htmlReports + "/cucumber_reporter.html", 18 | reportSuiteAsScenarios: true 19 | }; 20 | 21 | /** 22 | * @type { import("protractor").Config } 23 | */ 24 | exports.config = { 25 | allScriptsTimeout: 600000, 26 | pageLoadingTimeout: 60000, 27 | defaultTimeout: 30000, 28 | specs: [ 29 | './src/features/*.feature' 30 | ], 31 | capabilities: { 32 | 'browserName': 'chrome' 33 | }, 34 | directConnect: true, 35 | baseUrl: 'http://localhost:4200/', 36 | framework: "custom", 37 | frameworkPath: require.resolve("protractor-cucumber-framework"), 38 | cucumberOpts: { 39 | compiler: [], 40 | tags: [], 41 | format: "json:./e2e/reports/json/cucumber_report.json", 42 | require: ["./src/step-definitions/*.ts", "./src/support/*.ts"], 43 | strict: true 44 | }, 45 | onPrepare() { 46 | require('ts-node').register({ 47 | project: require('path').join(__dirname, './tsconfig.json') 48 | }); 49 | 50 | if (!fs.existsSync(jsonReports)) { 51 | mkdirp.sync(jsonReports); 52 | } 53 | }, 54 | onComplete: () => { 55 | reporter.generate(options); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /e2e/reports/json/cucumber_report.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keyword": "Funcionalidade", 4 | "name": "Autenticar usuario", 5 | "line": 3, 6 | "id": "autenticar-usuario", 7 | "tags": [], 8 | "uri": "e2e\\src\\features\\F01_Login.feature", 9 | "elements": [ 10 | { 11 | "id": "autenticar-usuario;login", 12 | "keyword": "Scenario", 13 | "line": 5, 14 | "name": "Login", 15 | "tags": [], 16 | "type": "scenario", 17 | "steps": [ 18 | { 19 | "keyword": "Before", 20 | "hidden": true, 21 | "match": { 22 | "location": "e2e\\src\\step-definitions\\home.spec.ts:13" 23 | }, 24 | "result": { 25 | "status": "passed", 26 | "duration": 1000000 27 | } 28 | }, 29 | { 30 | "keyword": "Before", 31 | "hidden": true, 32 | "match": { 33 | "location": "e2e\\src\\step-definitions\\login.spec.ts:11" 34 | }, 35 | "result": { 36 | "status": "passed", 37 | "duration": 1000000 38 | } 39 | }, 40 | { 41 | "arguments": [], 42 | "keyword": "Dado ", 43 | "line": 6, 44 | "name": "que estou na página inicial 'Login'", 45 | "match": { 46 | "location": "e2e\\src\\step-definitions\\login.spec.ts:15" 47 | }, 48 | "result": { 49 | "status": "passed", 50 | "duration": 3827000000 51 | } 52 | }, 53 | { 54 | "arguments": [], 55 | "keyword": "Quando ", 56 | "line": 7, 57 | "name": "devo preencher o usuario 'test'", 58 | "match": { 59 | "location": "e2e\\src\\step-definitions\\login.spec.ts:21" 60 | }, 61 | "result": { 62 | "status": "passed", 63 | "duration": 366000000 64 | } 65 | }, 66 | { 67 | "arguments": [], 68 | "keyword": "Quando ", 69 | "line": 8, 70 | "name": "devo preencher a senha '123'", 71 | "match": { 72 | "location": "e2e\\src\\step-definitions\\login.spec.ts:27" 73 | }, 74 | "result": { 75 | "status": "passed", 76 | "duration": 190000000 77 | } 78 | }, 79 | { 80 | "arguments": [], 81 | "keyword": "E ", 82 | "line": 9, 83 | "name": "devo clicar no botão 'Entrar'", 84 | "match": { 85 | "location": "e2e\\src\\step-definitions\\login.spec.ts:33" 86 | }, 87 | "result": { 88 | "status": "passed", 89 | "duration": 182000000 90 | } 91 | }, 92 | { 93 | "arguments": [], 94 | "keyword": "Então ", 95 | "line": 10, 96 | "name": "sou redirecionado para a página 'Home'", 97 | "match": { 98 | "location": "e2e\\src\\step-definitions\\login.spec.ts:47" 99 | }, 100 | "result": { 101 | "status": "passed", 102 | "duration": 1016000000 103 | } 104 | }, 105 | { 106 | "keyword": "After", 107 | "hidden": true, 108 | "match": { 109 | "location": "e2e\\src\\support\\hooks.ts:4" 110 | }, 111 | "result": { 112 | "status": "passed" 113 | } 114 | }, 115 | { 116 | "keyword": "After", 117 | "hidden": true, 118 | "match": { 119 | "location": "node_modules\\protractor-cucumber-framework\\lib\\resultsCapturer.js:26" 120 | }, 121 | "result": { 122 | "status": "passed" 123 | } 124 | } 125 | ] 126 | } 127 | ] 128 | }, 129 | { 130 | "keyword": "Funcionalidade", 131 | "name": "Motorista", 132 | "line": 3, 133 | "id": "motorista", 134 | "tags": [], 135 | "uri": "e2e\\src\\features\\F02_Motorista.feature", 136 | "elements": [ 137 | { 138 | "id": "motorista;novo-motorista", 139 | "keyword": "Scenario", 140 | "line": 5, 141 | "name": "Novo Motorista", 142 | "tags": [], 143 | "type": "scenario", 144 | "steps": [ 145 | { 146 | "keyword": "Before", 147 | "hidden": true, 148 | "match": { 149 | "location": "e2e\\src\\step-definitions\\home.spec.ts:13" 150 | }, 151 | "result": { 152 | "status": "passed", 153 | "duration": 1000000 154 | } 155 | }, 156 | { 157 | "keyword": "Before", 158 | "hidden": true, 159 | "match": { 160 | "location": "e2e\\src\\step-definitions\\login.spec.ts:11" 161 | }, 162 | "result": { 163 | "status": "passed" 164 | } 165 | }, 166 | { 167 | "arguments": [], 168 | "keyword": "Dado ", 169 | "line": 6, 170 | "name": "que eu estou na página 'Home'", 171 | "match": { 172 | "location": "e2e\\src\\step-definitions\\home.spec.ts:17" 173 | }, 174 | "result": { 175 | "status": "passed", 176 | "duration": 1026000000 177 | } 178 | }, 179 | { 180 | "arguments": [], 181 | "keyword": "Quando ", 182 | "line": 7, 183 | "name": "devo clicar no botão 'Novo Motorista'", 184 | "match": { 185 | "location": "e2e\\src\\step-definitions\\login.spec.ts:33" 186 | }, 187 | "result": { 188 | "status": "passed", 189 | "duration": 199000000 190 | } 191 | }, 192 | { 193 | "arguments": [], 194 | "keyword": "E ", 195 | "line": 8, 196 | "name": "devo visualizar o dialog 'Cadastrar Motorista'", 197 | "match": { 198 | "location": "e2e\\src\\step-definitions\\home.spec.ts:41" 199 | }, 200 | "result": { 201 | "status": "passed", 202 | "duration": 1058000000 203 | } 204 | }, 205 | { 206 | "arguments": [], 207 | "keyword": "Quando ", 208 | "line": 9, 209 | "name": "devo preencher o nome 'Leandro Mancini'", 210 | "match": { 211 | "location": "e2e\\src\\step-definitions\\home.spec.ts:47" 212 | }, 213 | "result": { 214 | "status": "passed", 215 | "duration": 292000000 216 | } 217 | }, 218 | { 219 | "arguments": [], 220 | "keyword": "Quando ", 221 | "line": 10, 222 | "name": "devo preencher a data de nasc. '06051988'", 223 | "match": { 224 | "location": "e2e\\src\\step-definitions\\home.spec.ts:54" 225 | }, 226 | "result": { 227 | "status": "passed", 228 | "duration": 207000000 229 | } 230 | }, 231 | { 232 | "arguments": [], 233 | "keyword": "Quando ", 234 | "line": 11, 235 | "name": "devo preencher o telefone '1199999999'", 236 | "match": { 237 | "location": "e2e\\src\\step-definitions\\home.spec.ts:61" 238 | }, 239 | "result": { 240 | "status": "passed", 241 | "duration": 333000000 242 | } 243 | }, 244 | { 245 | "arguments": [], 246 | "keyword": "Quando ", 247 | "line": 12, 248 | "name": "devo selecionar o tipo de documento 'CPF'", 249 | "match": { 250 | "location": "e2e\\src\\step-definitions\\home.spec.ts:68" 251 | }, 252 | "result": { 253 | "status": "passed", 254 | "duration": 188000000 255 | } 256 | }, 257 | { 258 | "arguments": [], 259 | "keyword": "E ", 260 | "line": 13, 261 | "name": "devo preencher o número '01234567890'", 262 | "match": { 263 | "location": "e2e\\src\\step-definitions\\home.spec.ts:74" 264 | }, 265 | "result": { 266 | "status": "passed", 267 | "duration": 289000000 268 | } 269 | }, 270 | { 271 | "arguments": [], 272 | "keyword": "Então ", 273 | "line": 14, 274 | "name": "devo clicar no botão 'Salvar' e grava um novo motorista", 275 | "match": { 276 | "location": "e2e\\src\\step-definitions\\home.spec.ts:81" 277 | }, 278 | "result": { 279 | "status": "passed", 280 | "duration": 632000000 281 | } 282 | }, 283 | { 284 | "keyword": "After", 285 | "hidden": true, 286 | "match": { 287 | "location": "e2e\\src\\support\\hooks.ts:4" 288 | }, 289 | "result": { 290 | "status": "passed" 291 | } 292 | }, 293 | { 294 | "keyword": "After", 295 | "hidden": true, 296 | "match": { 297 | "location": "node_modules\\protractor-cucumber-framework\\lib\\resultsCapturer.js:26" 298 | }, 299 | "result": { 300 | "status": "passed", 301 | "duration": 1000000 302 | } 303 | } 304 | ] 305 | }, 306 | { 307 | "id": "motorista;editar-motorista", 308 | "keyword": "Scenario", 309 | "line": 16, 310 | "name": "Editar Motorista", 311 | "tags": [], 312 | "type": "scenario", 313 | "steps": [ 314 | { 315 | "keyword": "Before", 316 | "hidden": true, 317 | "match": { 318 | "location": "e2e\\src\\step-definitions\\home.spec.ts:13" 319 | }, 320 | "result": { 321 | "status": "passed" 322 | } 323 | }, 324 | { 325 | "keyword": "Before", 326 | "hidden": true, 327 | "match": { 328 | "location": "e2e\\src\\step-definitions\\login.spec.ts:11" 329 | }, 330 | "result": { 331 | "status": "passed", 332 | "duration": 1000000 333 | } 334 | }, 335 | { 336 | "arguments": [], 337 | "keyword": "Dado ", 338 | "line": 17, 339 | "name": "que eu estou na página 'Home'", 340 | "match": { 341 | "location": "e2e\\src\\step-definitions\\home.spec.ts:17" 342 | }, 343 | "result": { 344 | "status": "passed", 345 | "duration": 1241000000 346 | } 347 | }, 348 | { 349 | "arguments": [], 350 | "keyword": "Quando ", 351 | "line": 18, 352 | "name": "devo clicar no botão 'Editar' da lista", 353 | "match": { 354 | "location": "e2e\\src\\step-definitions\\home.spec.ts:31" 355 | }, 356 | "result": { 357 | "status": "passed", 358 | "duration": 1725000000 359 | } 360 | }, 361 | { 362 | "arguments": [], 363 | "keyword": "E ", 364 | "line": 19, 365 | "name": "devo visualizar o dialog 'Editar Motorista'", 366 | "match": { 367 | "location": "e2e\\src\\step-definitions\\home.spec.ts:41" 368 | }, 369 | "result": { 370 | "status": "passed", 371 | "duration": 1077000000 372 | } 373 | }, 374 | { 375 | "arguments": [], 376 | "keyword": "Quando ", 377 | "line": 20, 378 | "name": "devo preencher o nome 'Nome editado'", 379 | "match": { 380 | "location": "e2e\\src\\step-definitions\\home.spec.ts:47" 381 | }, 382 | "result": { 383 | "status": "passed", 384 | "duration": 250000000 385 | } 386 | }, 387 | { 388 | "arguments": [], 389 | "keyword": "Quando ", 390 | "line": 21, 391 | "name": "devo preencher o telefone '1199999999'", 392 | "match": { 393 | "location": "e2e\\src\\step-definitions\\home.spec.ts:61" 394 | }, 395 | "result": { 396 | "status": "passed", 397 | "duration": 227000000 398 | } 399 | }, 400 | { 401 | "arguments": [], 402 | "keyword": "Quando ", 403 | "line": 22, 404 | "name": "devo selecionar o tipo de documento 'CPF'", 405 | "match": { 406 | "location": "e2e\\src\\step-definitions\\home.spec.ts:68" 407 | }, 408 | "result": { 409 | "status": "passed", 410 | "duration": 122000000 411 | } 412 | }, 413 | { 414 | "arguments": [], 415 | "keyword": "E ", 416 | "line": 23, 417 | "name": "devo preencher o número '01234567890'", 418 | "match": { 419 | "location": "e2e\\src\\step-definitions\\home.spec.ts:74" 420 | }, 421 | "result": { 422 | "status": "passed", 423 | "duration": 226000000 424 | } 425 | }, 426 | { 427 | "arguments": [], 428 | "keyword": "Então ", 429 | "line": 24, 430 | "name": "devo clicar no botão 'Salvar' e grava um novo motorista", 431 | "match": { 432 | "location": "e2e\\src\\step-definitions\\home.spec.ts:81" 433 | }, 434 | "result": { 435 | "status": "passed", 436 | "duration": 672000000 437 | } 438 | }, 439 | { 440 | "keyword": "After", 441 | "hidden": true, 442 | "match": { 443 | "location": "e2e\\src\\support\\hooks.ts:4" 444 | }, 445 | "result": { 446 | "status": "passed" 447 | } 448 | }, 449 | { 450 | "keyword": "After", 451 | "hidden": true, 452 | "match": { 453 | "location": "node_modules\\protractor-cucumber-framework\\lib\\resultsCapturer.js:26" 454 | }, 455 | "result": { 456 | "status": "passed" 457 | } 458 | } 459 | ] 460 | }, 461 | { 462 | "id": "motorista;inativar-motorista", 463 | "keyword": "Scenario", 464 | "line": 26, 465 | "name": "Inativar Motorista", 466 | "tags": [], 467 | "type": "scenario", 468 | "steps": [ 469 | { 470 | "keyword": "Before", 471 | "hidden": true, 472 | "match": { 473 | "location": "e2e\\src\\step-definitions\\home.spec.ts:13" 474 | }, 475 | "result": { 476 | "status": "passed", 477 | "duration": 1000000 478 | } 479 | }, 480 | { 481 | "keyword": "Before", 482 | "hidden": true, 483 | "match": { 484 | "location": "e2e\\src\\step-definitions\\login.spec.ts:11" 485 | }, 486 | "result": { 487 | "status": "passed", 488 | "duration": 1000000 489 | } 490 | }, 491 | { 492 | "arguments": [], 493 | "keyword": "Dado ", 494 | "line": 27, 495 | "name": "que eu estou na página 'Home'", 496 | "match": { 497 | "location": "e2e\\src\\step-definitions\\home.spec.ts:17" 498 | }, 499 | "result": { 500 | "status": "passed", 501 | "duration": 1431000000 502 | } 503 | }, 504 | { 505 | "arguments": [], 506 | "keyword": "Então ", 507 | "line": 28, 508 | "name": "devo ativar ou inativar um item da lista", 509 | "match": { 510 | "location": "e2e\\src\\step-definitions\\home.spec.ts:91" 511 | }, 512 | "result": { 513 | "status": "passed", 514 | "duration": 790000000 515 | } 516 | }, 517 | { 518 | "keyword": "After", 519 | "hidden": true, 520 | "match": { 521 | "location": "e2e\\src\\support\\hooks.ts:4" 522 | }, 523 | "result": { 524 | "status": "passed", 525 | "duration": 1000000 526 | } 527 | }, 528 | { 529 | "keyword": "After", 530 | "hidden": true, 531 | "match": { 532 | "location": "node_modules\\protractor-cucumber-framework\\lib\\resultsCapturer.js:26" 533 | }, 534 | "result": { 535 | "status": "passed" 536 | } 537 | } 538 | ] 539 | } 540 | ] 541 | }, 542 | { 543 | "keyword": "Funcionalidade", 544 | "name": "Deslogar usuario", 545 | "line": 3, 546 | "id": "deslogar-usuario", 547 | "tags": [], 548 | "uri": "e2e\\src\\features\\F03_Logout.feature", 549 | "elements": [ 550 | { 551 | "id": "deslogar-usuario;logout", 552 | "keyword": "Scenario", 553 | "line": 5, 554 | "name": "Logout", 555 | "tags": [], 556 | "type": "scenario", 557 | "steps": [ 558 | { 559 | "keyword": "Before", 560 | "hidden": true, 561 | "match": { 562 | "location": "e2e\\src\\step-definitions\\home.spec.ts:13" 563 | }, 564 | "result": { 565 | "status": "passed", 566 | "duration": 1000000 567 | } 568 | }, 569 | { 570 | "keyword": "Before", 571 | "hidden": true, 572 | "match": { 573 | "location": "e2e\\src\\step-definitions\\login.spec.ts:11" 574 | }, 575 | "result": { 576 | "status": "passed" 577 | } 578 | }, 579 | { 580 | "arguments": [], 581 | "keyword": "Dado ", 582 | "line": 6, 583 | "name": "que eu estou na página 'Home'", 584 | "match": { 585 | "location": "e2e\\src\\step-definitions\\home.spec.ts:17" 586 | }, 587 | "result": { 588 | "status": "passed", 589 | "duration": 1072000000 590 | } 591 | }, 592 | { 593 | "arguments": [], 594 | "keyword": "Quando ", 595 | "line": 7, 596 | "name": "devo clicar no botão perfil", 597 | "match": { 598 | "location": "e2e\\src\\step-definitions\\login.spec.ts:53" 599 | }, 600 | "result": { 601 | "status": "passed", 602 | "duration": 146000000 603 | } 604 | }, 605 | { 606 | "arguments": [], 607 | "keyword": "E ", 608 | "line": 8, 609 | "name": "devo clicar no botão logout 'SAIR'", 610 | "match": { 611 | "location": "e2e\\src\\step-definitions\\login.spec.ts:59" 612 | }, 613 | "result": { 614 | "status": "passed", 615 | "duration": 688000000 616 | } 617 | }, 618 | { 619 | "arguments": [], 620 | "keyword": "Então ", 621 | "line": 9, 622 | "name": "sou redirecionado para a página 'Login'", 623 | "match": { 624 | "location": "e2e\\src\\step-definitions\\login.spec.ts:47" 625 | }, 626 | "result": { 627 | "status": "passed", 628 | "duration": 1018000000 629 | } 630 | }, 631 | { 632 | "keyword": "After", 633 | "hidden": true, 634 | "match": { 635 | "location": "e2e\\src\\support\\hooks.ts:4" 636 | }, 637 | "result": { 638 | "status": "passed" 639 | } 640 | }, 641 | { 642 | "keyword": "After", 643 | "hidden": true, 644 | "match": { 645 | "location": "node_modules\\protractor-cucumber-framework\\lib\\resultsCapturer.js:26" 646 | }, 647 | "result": { 648 | "status": "passed", 649 | "duration": 1000000 650 | } 651 | } 652 | ] 653 | } 654 | ] 655 | } 656 | ] -------------------------------------------------------------------------------- /e2e/src/config/chai-imports.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as cap from 'chai-as-promised'; 3 | 4 | chai.use(cap); 5 | 6 | export const expect = chai.expect; 7 | export const assert = chai.assert; 8 | export const should = chai.should(); -------------------------------------------------------------------------------- /e2e/src/features/F01_Login.feature: -------------------------------------------------------------------------------- 1 | #language: pt 2 | 3 | Funcionalidade: Autenticar usuario 4 | 5 | Cenário: Login 6 | Dado que estou na página inicial 'Login' 7 | Quando devo preencher o usuario 'test' 8 | Quando devo preencher a senha '123' 9 | E devo clicar no botão 'Entrar' 10 | Então sou redirecionado para a página 'Home' 11 | -------------------------------------------------------------------------------- /e2e/src/features/F02_Motorista.feature: -------------------------------------------------------------------------------- 1 | #language: pt 2 | 3 | Funcionalidade: Motorista 4 | 5 | Cenário: Novo Motorista 6 | Dado que eu estou na página 'Home' 7 | Quando devo clicar no botão 'Novo Motorista' 8 | E devo visualizar o dialog 'Cadastrar Motorista' 9 | Quando devo preencher o nome 'Leandro Mancini' 10 | Quando devo preencher a data de nasc. '06051988' 11 | Quando devo preencher o telefone '1199999999' 12 | Quando devo selecionar o tipo de documento 'CPF' 13 | E devo preencher o número '01234567890' 14 | Então devo clicar no botão 'Salvar' e grava um novo motorista 15 | 16 | Cenário: Editar Motorista 17 | Dado que eu estou na página 'Home' 18 | Quando devo clicar no botão 'Editar' da lista 19 | E devo visualizar o dialog 'Editar Motorista' 20 | Quando devo preencher o nome 'Nome editado' 21 | Quando devo preencher o telefone '1199999999' 22 | Quando devo selecionar o tipo de documento 'CPF' 23 | E devo preencher o número '01234567890' 24 | Então devo clicar no botão 'Salvar' e grava um novo motorista 25 | 26 | Cenário: Inativar Motorista 27 | Dado que eu estou na página 'Home' 28 | Então devo ativar ou inativar um item da lista 29 | -------------------------------------------------------------------------------- /e2e/src/features/F03_Logout.feature: -------------------------------------------------------------------------------- 1 | #language: pt 2 | 3 | Funcionalidade: Deslogar usuario 4 | 5 | Cenário: Logout 6 | Dado que eu estou na página 'Home' 7 | Quando devo clicar no botão perfil 8 | E devo clicar no botão logout 'SAIR' 9 | Então sou redirecionado para a página 'Login' 10 | -------------------------------------------------------------------------------- /e2e/src/step-definitions/home.spec.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'protractor'; 2 | import { Given, Then, When, Before, setDefaultTimeout } from 'cucumber'; 3 | import { expect } from './../config/chai-imports'; 4 | 5 | import { HomePage } from './pages/home.po'; 6 | import { DialogPage } from './pages/dialog.po'; 7 | 8 | const home: HomePage = new HomePage(); 9 | const dialog: DialogPage = new DialogPage(); 10 | 11 | setDefaultTimeout(browser.defaultTimeout); 12 | 13 | Before(() => { 14 | browser.manage().window().maximize(); 15 | }); 16 | 17 | Given('que eu estou na página {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 18 | await expect(await browser.getTitle()).to.equal(text); 19 | 20 | await browser.sleep(1000); 21 | }); 22 | 23 | When('devo clicar no botão {string} para adicionar', { timeout: browser.pageLoadingTimeout }, async () => { 24 | await browser.actions().mouseMove(home.btnNovoMotorista).perform().then(() => { 25 | home.btnNovoMotorista.click(); 26 | }); 27 | 28 | await browser.sleep(1000); 29 | }); 30 | 31 | When('devo clicar no botão {string} da lista', { timeout: browser.pageLoadingTimeout }, async (text) => { 32 | await browser.sleep(500); 33 | 34 | await browser.actions().mouseMove(home.btnEditar).perform().then(() => { 35 | home.btnEditar.click(); 36 | }); 37 | 38 | await browser.sleep(1000); 39 | }); 40 | 41 | When('devo visualizar o dialog {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 42 | await expect(await dialog.titulo.getText()).to.contain(text); 43 | 44 | await browser.sleep(1000); 45 | }); 46 | 47 | When('devo preencher o nome {string}', { timeout: browser.pageLoadingTimeout }, async (value) => { 48 | await browser.actions().mouseMove(dialog.inputNome).perform().then(() => { 49 | dialog.inputNome.clear(); 50 | dialog.inputNome.sendKeys(value); 51 | }); 52 | }); 53 | 54 | When('devo preencher a data de nasc. {string}', { timeout: browser.pageLoadingTimeout }, async (value) => { 55 | await browser.actions().mouseMove(dialog.inputDataNascimento).perform().then(() => { 56 | dialog.inputDataNascimento.clear(); 57 | dialog.inputDataNascimento.sendKeys(value); 58 | }); 59 | }); 60 | 61 | When('devo preencher o telefone {string}', { timeout: browser.pageLoadingTimeout }, async (value) => { 62 | await browser.actions().mouseMove(dialog.inputTelefone).perform().then(() => { 63 | dialog.inputTelefone.clear(); 64 | dialog.inputTelefone.sendKeys(value); 65 | }); 66 | }); 67 | 68 | When('devo selecionar o tipo de documento {string}', { timeout: browser.pageLoadingTimeout }, async (value) => { 69 | await browser.actions().mouseMove(dialog.inputTipoDocumento).perform().then(() => { 70 | dialog.inputTipoDocumento.click(); 71 | }); 72 | }); 73 | 74 | When('devo preencher o número {string}', { timeout: browser.pageLoadingTimeout }, async (value) => { 75 | await browser.actions().mouseMove(dialog.inputNumero).perform().then(() => { 76 | dialog.inputNumero.clear(); 77 | dialog.inputNumero.sendKeys(value); 78 | }); 79 | }); 80 | 81 | Then('devo clicar no botão {string} e grava um novo motorista', { timeout: browser.pageLoadingTimeout }, async (text) => { 82 | await browser.sleep(500); 83 | 84 | await expect(await dialog.btnSalvar.getText()).to.contain(text); 85 | 86 | await browser.actions().mouseMove(dialog.btnSalvar).perform().then(() => { 87 | dialog.btnSalvar.click(); 88 | }); 89 | }); 90 | 91 | Then('devo ativar ou inativar um item da lista', { timeout: browser.pageLoadingTimeout }, async () => { 92 | await browser.sleep(500); 93 | 94 | await browser.actions().mouseMove(home.btnInativar).perform().then(() => { 95 | home.btnInativar.click(); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /e2e/src/step-definitions/login.spec.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'protractor'; 2 | import { Given, Then, When, Before, setDefaultTimeout } from 'cucumber'; 3 | import { expect } from './../config/chai-imports'; 4 | 5 | import { LoginPage } from './pages/login.po'; 6 | 7 | const login: LoginPage = new LoginPage(); 8 | 9 | setDefaultTimeout(browser.defaultTimeout); 10 | 11 | Before(() => { 12 | browser.manage().window().maximize(); 13 | }); 14 | 15 | Given('que estou na página inicial {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 16 | await login.navigateTo(); 17 | 18 | await expect(await browser.getTitle()).to.equal(text); 19 | }); 20 | 21 | When('devo preencher o usuario {string}', { timeout: browser.pageLoadingTimeout }, async (username) => { 22 | await browser.actions().mouseMove(login.username).perform().then(() => { 23 | login.username.sendKeys(username); 24 | }); 25 | }); 26 | 27 | When('devo preencher a senha {string}', { timeout: browser.pageLoadingTimeout }, async (senha) => { 28 | await browser.actions().mouseMove(login.senha).perform().then(() => { 29 | login.senha.sendKeys(senha); 30 | }); 31 | }); 32 | 33 | When('devo clicar no botão {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 34 | await expect(await login.buttonEnter.getText()).to.equal(text); 35 | 36 | await browser.actions().mouseMove(login.buttonEnter).perform().then(() => { 37 | login.buttonEnter.click(); 38 | }); 39 | }); 40 | 41 | Then('devo visualizar a mensagem de {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 42 | await browser.sleep(500); 43 | 44 | await expect(await login.bemVindo.getText()).to.contain(text); 45 | }); 46 | 47 | Then('sou redirecionado para a página {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 48 | await browser.sleep(1000); 49 | 50 | await expect(await browser.getTitle()).to.equal(text); 51 | }); 52 | 53 | When('devo clicar no botão perfil', { timeout: browser.pageLoadingTimeout }, async () => { 54 | await browser.actions().mouseMove(login.buttonUser).perform().then(() => { 55 | login.buttonUser.click(); 56 | }); 57 | }); 58 | 59 | Given('devo clicar no botão logout {string}', { timeout: browser.pageLoadingTimeout }, async (text) => { 60 | await browser.sleep(500); 61 | 62 | await expect(await login.buttonLogout.getText()).to.equal(text); 63 | 64 | await browser.actions().mouseMove(login.buttonLogout).perform().then(() => { 65 | login.buttonLogout.click(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /e2e/src/step-definitions/pages/dialog.po.ts: -------------------------------------------------------------------------------- 1 | import { $, ElementFinder, browser } from 'protractor'; 2 | 3 | export class DialogPage { 4 | titulo: ElementFinder; 5 | inputNome: ElementFinder; 6 | inputDataNascimento: ElementFinder; 7 | inputTelefone: ElementFinder; 8 | inputTipoDocumento: ElementFinder; 9 | inputNumero: ElementFinder; 10 | btnSalvar: ElementFinder; 11 | 12 | constructor() { 13 | this.titulo = $('app-dialog-cadastro .mat-dialog-title'); 14 | this.inputNome = $('app-dialog-cadastro app-input[formcontrolname="name"] input'); 15 | this.inputDataNascimento = $('app-dialog-cadastro app-input[formControlName="birth_date"] input'); 16 | this.inputTelefone = $('app-dialog-cadastro app-input[formControlName="phone"] input'); 17 | this.inputTipoDocumento = $('app-dialog-cadastro .t-radio-group .mat-radio-button:nth-child(1)'); 18 | this.inputNumero = $('app-dialog-cadastro app-input[formControlName="number"] input'); 19 | this.btnSalvar = $('app-dialog-cadastro button[type="submit"]'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /e2e/src/step-definitions/pages/home.po.ts: -------------------------------------------------------------------------------- 1 | import { $, ElementFinder, browser } from 'protractor'; 2 | 3 | export class HomePage { 4 | btnNovoMotorista: ElementFinder; 5 | btnEditar: ElementFinder; 6 | btnInativar: ElementFinder; 7 | 8 | constructor() { 9 | this.btnNovoMotorista = $('.container .btn-new-driver'); 10 | this.btnEditar = $('.mat-table .btn-edit'); 11 | this.btnInativar = $('.mat-table .btn-enable'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /e2e/src/step-definitions/pages/login.po.ts: -------------------------------------------------------------------------------- 1 | import { $, ElementFinder, browser } from 'protractor'; 2 | 3 | export class LoginPage { 4 | username: ElementFinder; 5 | senha: ElementFinder; 6 | buttonEnter: ElementFinder; 7 | bemVindo: ElementFinder; 8 | buttonUser: ElementFinder; 9 | buttonLogout: ElementFinder; 10 | 11 | constructor() { 12 | this.username = $('[formcontrolname="username"] input'); 13 | this.senha = $('[formcontrolname="password"] input'); 14 | this.buttonEnter = $('button.mat-primary'); 15 | this.buttonUser = $('.mat-toolbar .mat-icon-button'); 16 | this.buttonLogout = $('.menu-card .mat-button'); 17 | } 18 | 19 | navigateTo() { 20 | return browser.get('/'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /e2e/src/support/hooks.ts: -------------------------------------------------------------------------------- 1 | const { BeforeAll, After, AfterAll, Status } = require('cucumber'); 2 | import { browser } from 'protractor'; 3 | 4 | After(async function(scenario) { 5 | if (scenario.result.status === Status.FAILED) { 6 | // screenShot is a base-64 encoded PNG 7 | const screenShot = await browser.takeScreenshot(); 8 | this.attach(screenShot, 'image/png'); 9 | } 10 | }); 11 | 12 | AfterAll({timeout: 100 * 1000}, async () => { 13 | await browser.quit(); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular-clean-architecture'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true, 22 | thresholds: { 23 | statements: 80, 24 | lines: 80, 25 | branches: 80, 26 | functions: 80 27 | } 28 | }, 29 | reporters: ['progress', 'kjhtml'], 30 | port: 9876, 31 | colors: true, 32 | logLevel: config.LOG_INFO, 33 | autoWatch: true, 34 | browsers: ['Chrome'], 35 | singleRun: false, 36 | restartOnFileChange: true 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/favicon.ico", 11 | "/index.html", 12 | "/*.css", 13 | "/*.js" 14 | ] 15 | } 16 | }, { 17 | "name": "assets", 18 | "installMode": "lazy", 19 | "updateMode": "prefetch", 20 | "resources": { 21 | "files": [ 22 | "/assets/**", 23 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 24 | ] 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-clean-architecture", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build --prod", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e", 11 | "sw": "npm run build -s && npx http-server ./dist -p 4200", 12 | "server": "json-server ./src/backend/server.js", 13 | "translations:extract": "ngx-translate-extract --input ./src --output ./src/assets/i18n/template.json --format=json --clean -sort --marker extract", 14 | "storybook": "start-storybook -p 6006", 15 | "build-storybook": "build-storybook" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/animations": "~8.0.1", 20 | "@angular/cdk": "~8.1.3", 21 | "@angular/common": "~8.0.1", 22 | "@angular/compiler": "~8.0.1", 23 | "@angular/core": "~8.0.1", 24 | "@angular/flex-layout": "^8.0.0-beta.26", 25 | "@angular/forms": "~8.0.1", 26 | "@angular/material": "^8.1.3", 27 | "@angular/platform-browser": "~8.0.1", 28 | "@angular/platform-browser-dynamic": "~8.0.1", 29 | "@angular/pwa": "^0.803.0", 30 | "@angular/router": "~8.0.1", 31 | "@angular/service-worker": "~8.0.1", 32 | "@ngx-translate/core": "^11.0.1", 33 | "@ngx-translate/http-loader": "^4.0.0", 34 | "automapper-ts": "^1.9.0", 35 | "bootstrap": "^4.3.1", 36 | "hammerjs": "^2.0.8", 37 | "rxjs": "~6.4.0", 38 | "ts.validator.fluent": "^1.3.0", 39 | "tslib": "^1.9.0", 40 | "zone.js": "~0.9.1" 41 | }, 42 | "devDependencies": { 43 | "@angular-devkit/build-angular": "~0.800.0", 44 | "@angular/cli": "~8.0.3", 45 | "@angular/compiler-cli": "~8.0.1", 46 | "@angular/language-service": "~8.0.1", 47 | "@babel/core": "^7.5.5", 48 | "@biesbjerg/ngx-translate-extract": "^3.0.3", 49 | "@storybook/addon-actions": "^5.1.11", 50 | "@storybook/addon-links": "^5.1.11", 51 | "@storybook/addon-notes": "^5.1.11", 52 | "@storybook/addons": "^5.1.11", 53 | "@storybook/angular": "^5.1.11", 54 | "@types/cucumber": "^4.0.7", 55 | "@types/jasmine": "~3.3.8", 56 | "@types/jasminewd2": "~2.0.3", 57 | "@types/node": "~8.9.4", 58 | "babel-loader": "^8.0.6", 59 | "chai": "^4.2.0", 60 | "chai-as-promised": "^7.1.1", 61 | "codelyzer": "^5.0.0", 62 | "cucumber": "^5.1.0", 63 | "cucumber-html-reporter": "^5.0.0", 64 | "jasmine-core": "~3.4.0", 65 | "jasmine-spec-reporter": "~4.2.1", 66 | "karma": "~4.1.0", 67 | "karma-chrome-launcher": "~2.2.0", 68 | "karma-coverage-istanbul-reporter": "~2.0.1", 69 | "karma-jasmine": "~2.0.1", 70 | "karma-jasmine-html-reporter": "^1.4.0", 71 | "lodash": "^4.17.15", 72 | "moment": "^2.24.0", 73 | "protractor": "~5.4.0", 74 | "protractor-cucumber-framework": "^6.1.3", 75 | "ts-node": "~7.0.0", 76 | "tslint": "~5.15.0", 77 | "typescript": "~3.4.3" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /print-ng-e2e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/print-ng-e2e.png -------------------------------------------------------------------------------- /print-ng-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/print-ng-test.png -------------------------------------------------------------------------------- /src/app/app-material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { 3 | MatAutocompleteModule, 4 | MatButtonModule, 5 | MatButtonToggleModule, 6 | MatCardModule, 7 | MatCheckboxModule, 8 | MatChipsModule, 9 | MatCommonModule, 10 | MatDatepickerModule, 11 | MatDialogModule, 12 | MatDividerModule, 13 | MatExpansionModule, 14 | MatFormFieldModule, 15 | MatGridListModule, 16 | MatIconModule, 17 | MatInputModule, 18 | MatLineModule, 19 | MatListModule, 20 | MatMenuModule, 21 | MatNativeDateModule, 22 | MatOptionModule, 23 | MatPaginatorModule, 24 | MatProgressBarModule, 25 | MatProgressSpinnerModule, 26 | MatPseudoCheckboxModule, 27 | MatRadioModule, 28 | MatRippleModule, 29 | MatSelectModule, 30 | MatSidenavModule, 31 | MatSliderModule, 32 | MatSlideToggleModule, 33 | MatSnackBarModule, 34 | MatSortModule, 35 | MatStepperModule, 36 | MatTableModule, 37 | MatTabsModule, 38 | MatToolbarModule, 39 | MatTooltipModule, 40 | } from '@angular/material'; 41 | 42 | @NgModule({ 43 | exports: [ 44 | MatAutocompleteModule, 45 | MatButtonModule, 46 | MatButtonToggleModule, 47 | MatCardModule, 48 | MatCheckboxModule, 49 | MatChipsModule, 50 | MatCommonModule, 51 | MatDatepickerModule, 52 | MatDialogModule, 53 | MatDividerModule, 54 | MatExpansionModule, 55 | MatFormFieldModule, 56 | MatGridListModule, 57 | MatIconModule, 58 | MatInputModule, 59 | MatLineModule, 60 | MatListModule, 61 | MatMenuModule, 62 | MatNativeDateModule, 63 | MatOptionModule, 64 | MatPaginatorModule, 65 | MatProgressBarModule, 66 | MatProgressSpinnerModule, 67 | MatPseudoCheckboxModule, 68 | MatRadioModule, 69 | MatRippleModule, 70 | MatSelectModule, 71 | MatSidenavModule, 72 | MatSlideToggleModule, 73 | MatSliderModule, 74 | MatSnackBarModule, 75 | MatSortModule, 76 | MatStepperModule, 77 | MatTableModule, 78 | MatTabsModule, 79 | MatToolbarModule, 80 | MatTooltipModule 81 | ] 82 | }) 83 | export class AppMaterialModule { } 84 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, ComponentFixture } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | import { I18nService } from './infra/translations/i18n.service'; 5 | import { TranslateModule } from '@ngx-translate/core'; 6 | 7 | describe('AppComponent:', () => { 8 | let i18nService: I18nService; 9 | let component: AppComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | RouterTestingModule, 16 | TranslateModule.forRoot() 17 | ], 18 | declarations: [ 19 | AppComponent 20 | ], 21 | providers: [ 22 | I18nService 23 | ] 24 | }).compileComponents(); 25 | 26 | i18nService = TestBed.get(I18nService); 27 | })); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(AppComponent); 31 | component = fixture.componentInstance; 32 | fixture.detectChanges(); 33 | }); 34 | 35 | it('deve criar o aplicativo', () => { 36 | expect(component).toBeTruthy(); 37 | }); 38 | 39 | it('deve iniciar o setup translations', () => { 40 | component.ngOnInit(); 41 | 42 | const defaultLanguage = 'pt_BR'; 43 | const supportedLanguages = ['pt_BR']; 44 | 45 | i18nService.init(defaultLanguage, supportedLanguages); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, NavigationEnd, ActivatedRoute } from '@angular/router'; 3 | import { Title } from '@angular/platform-browser'; 4 | import { TranslateService } from '@ngx-translate/core'; 5 | import { merge } from 'rxjs'; 6 | import { filter, map, mergeMap } from 'rxjs/operators'; 7 | 8 | import { I18nService } from './infra/translations/i18n.service'; 9 | import { environment } from 'src/environments/environment'; 10 | 11 | @Component({ 12 | selector: 'app-root', 13 | templateUrl: './app.component.html', 14 | styleUrls: ['./app.component.scss'] 15 | }) 16 | export class AppComponent implements OnInit { 17 | 18 | constructor( 19 | private router: Router, 20 | private activatedRoute: ActivatedRoute, 21 | private titleService: Title, 22 | private translateService: TranslateService, 23 | private i18nService: I18nService, 24 | ) { } 25 | 26 | ngOnInit() { 27 | 28 | // Setup translations 29 | this.i18nService.init(environment.defaultLanguage, environment.supportedLanguages); 30 | 31 | const onNavigationEnd = this.router.events.pipe(filter(event => event instanceof NavigationEnd)); 32 | 33 | merge(this.translateService.onLangChange, onNavigationEnd) 34 | .pipe( 35 | map(() => { 36 | let route = this.activatedRoute; 37 | while (route.firstChild) { 38 | route = route.firstChild; 39 | } 40 | return route; 41 | }), 42 | filter(route => route.outlet === 'primary'), 43 | mergeMap(route => route.data) 44 | ) 45 | .subscribe(event => { 46 | const title = event['title']; 47 | if (title) { 48 | this.titleService.setTitle(this.translateService.instant(title)); 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { ServiceWorkerModule } from '@angular/service-worker'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { PresentationModule } from './presentation/presentation.module'; 8 | import { InfraModule } from './infra/infra.module'; 9 | import { DataModule } from './data/data.module'; 10 | import { environment } from '../environments/environment'; 11 | import { DomainModule } from './domain/domain.module'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | BrowserAnimationsModule, 20 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), 21 | DomainModule, 22 | DataModule, 23 | InfraModule, 24 | PresentationModule 25 | ], 26 | providers: [], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /src/app/data/base/mapper.ts: -------------------------------------------------------------------------------- 1 | export abstract class Mapper { 2 | abstract mapFrom(param: O): E; 3 | 4 | abstract mapTo(param: O): E; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/app/data/data.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { IUsuarioRepository } from '../domain/interfaces/repository/iusuario-repository'; 5 | import { UsuarioRepository } from './repository/usuario/usuario-repository'; 6 | import { IMotoristaRepository } from '../domain/interfaces/repository/imotorista-repository'; 7 | import { MotoristaRepositoryService } from './repository/motorista/motorista-repository.service'; 8 | 9 | 10 | 11 | @NgModule({ 12 | declarations: [], 13 | imports: [ 14 | CommonModule, 15 | HttpClientModule 16 | ], 17 | providers: [ 18 | { provide: IUsuarioRepository, useClass: UsuarioRepository }, 19 | { provide: IMotoristaRepository, useClass: MotoristaRepositoryService } 20 | ] 21 | }) 22 | export class DataModule { } 23 | -------------------------------------------------------------------------------- /src/app/data/repository/motorista/motorista-mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { MotoristaMapper } from './motorista-mapper'; 2 | 3 | describe('MotoristaMapper', () => { 4 | xit('should create an instance', () => { 5 | expect(new MotoristaMapper()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/data/repository/motorista/motorista-mapper.ts: -------------------------------------------------------------------------------- 1 | import * as automapper from 'automapper-ts'; 2 | import * as moment from 'moment'; 3 | 4 | import { Mapper } from '../../base/mapper'; 5 | import { DriverEntity } from '../../../domain/entities/driver-entity'; 6 | export class MotoristaMapper implements Mapper { 7 | 8 | mapFrom(param: DriverEntity): DriverEntity { 9 | automapper 10 | .createMap('DriverEntity', DriverEntity) 11 | .forMember('birth_date', opts => opts.mapFrom('birth_date')) 12 | .forMember('birth_date', opts => moment(opts.sourceObject[opts.sourcePropertyName]).format('YYYY-MM-DD')); 13 | 14 | return automapper.map('DriverEntity', DriverEntity, param); 15 | } 16 | 17 | mapTo(param: DriverEntity): DriverEntity { 18 | automapper 19 | .createMap('DriverEntity', DriverEntity); 20 | 21 | return automapper.map('DriverEntity', DriverEntity, param); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/data/repository/motorista/motorista-repository.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MotoristaRepositoryService } from './motorista-repository.service'; 4 | import { HttpTestingController, HttpClientTestingModule, TestRequest } from '@angular/common/http/testing'; 5 | import { environment } from '../../../../environments/environment'; 6 | import { MotoristaModel } from 'src/app/core/domain/entity/motorista-model'; 7 | import { UsuarioModel } from 'src/app/core/domain/entity/usuario-model'; 8 | 9 | describe('MotoristaRepositoryService:', () => { 10 | let service: MotoristaRepositoryService; 11 | let backend: HttpTestingController; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | imports: [ 16 | HttpClientTestingModule 17 | ], 18 | providers: [ 19 | MotoristaRepositoryService 20 | ] 21 | }); 22 | 23 | backend = TestBed.get(HttpTestingController); 24 | service = TestBed.get(MotoristaRepositoryService); 25 | }); 26 | 27 | it('deve ser criado', () => { 28 | expect(service).toBeTruthy(); 29 | }); 30 | 31 | describe('quando fizer obter os motoristas', () => { 32 | it('deve fazer uma solicitação GET passando um ID', () => { 33 | service.get(1).subscribe(); 34 | 35 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 36 | expect(req.request.method).toBe('GET'); 37 | 38 | backend.verify(); 39 | }); 40 | 41 | it('deve fazer uma solicitação GET', () => { 42 | service.get().subscribe(); 43 | 44 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 45 | expect(req.request.method).toBe('GET'); 46 | 47 | backend.verify(); 48 | }); 49 | 50 | it('deve retornar um motorista', () => { 51 | service.get(1).subscribe((item) => { 52 | if (item) { 53 | expect(item).toBeTruthy(); 54 | } 55 | }); 56 | 57 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 58 | 59 | req.flush([{ 60 | id: 1, 61 | name: 'parolho', 62 | birth_date: '2019-01-01', 63 | phone: '123456789', 64 | state: true, 65 | city: 'SP', 66 | enable: true, 67 | addresses: 'São Paulo', 68 | documents: [] 69 | }]); 70 | 71 | backend.verify(); 72 | }); 73 | 74 | it('deve retornar um array de motoristas', () => { 75 | service.get(1).subscribe((item) => { 76 | expect(item).toEqual(null); 77 | }); 78 | 79 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 80 | 81 | req.flush([null]); 82 | 83 | backend.verify(); 84 | }); 85 | 86 | it('deve retornar null ao executar o metodo login', () => { 87 | service.get().subscribe(res => { 88 | expect(res).toEqual(null); 89 | }); 90 | 91 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 92 | 93 | req.flush([null]); 94 | }); 95 | }); 96 | 97 | describe('quando inserir um motorista', () => { 98 | it('deve fazer uma solicitação POST', () => { 99 | const motorista = new MotoristaModel(); 100 | 101 | motorista.name = 'test'; 102 | 103 | service.insert(motorista).subscribe(); 104 | 105 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 106 | expect(req.request.method).toBe('POST'); 107 | 108 | backend.verify(); 109 | }); 110 | }); 111 | 112 | describe('quando editar um motorista', () => { 113 | xit('deve fazer uma solicitação PUT', () => { 114 | const motorista = new MotoristaModel(); 115 | 116 | motorista.name = 'test'; 117 | 118 | service.update(motorista).subscribe(() => {}); 119 | 120 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 121 | expect(req.request.method).toBe('PUT'); 122 | 123 | backend.verify(); 124 | }); 125 | }); 126 | 127 | describe('quando inativar ou habilitar um motorista', () => { 128 | xit('deve fazer uma solicitação PATCH', () => { 129 | service.disableEnable(1, true).subscribe(() => {}); 130 | 131 | const req = backend.expectOne(environment.serverUrl + '/motoristas'); 132 | expect(req.request.method).toBe('PATCH'); 133 | 134 | backend.verify(); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /src/app/data/repository/motorista/motorista-repository.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { HttpClient } from '@angular/common/http'; 4 | 5 | import { MotoristaMapper } from './motorista-mapper'; 6 | import { environment } from 'src/environments/environment'; 7 | import { map, flatMap } from 'rxjs/operators'; 8 | import { DriverEntity } from '../../../domain/entities/driver-entity'; 9 | import { IMotoristaRepository } from 'src/app/domain/interfaces/repository/imotorista-repository'; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class MotoristaRepositoryService implements IMotoristaRepository { 15 | 16 | private mapper = new MotoristaMapper(); 17 | 18 | constructor(private http: HttpClient) { } 19 | 20 | get(id?: number): Observable { 21 | if (id) { 22 | return this.http 23 | .get(environment.serverUrl + '/motoristas') 24 | .pipe(map((item) => { 25 | if (item[0]) { 26 | return this.mapper.mapFrom(item[0]); 27 | } 28 | 29 | return null; 30 | })); 31 | } else { 32 | return this.http 33 | .get(environment.serverUrl + '/motoristas') 34 | .pipe(flatMap((item) => item)) 35 | .pipe(map(this.mapper.mapFrom)); 36 | } 37 | } 38 | insert(param: DriverEntity): Observable { 39 | return this.http 40 | .post(environment.serverUrl + '/motoristas', param) 41 | .pipe(map(this.mapper.mapFrom)); 42 | } 43 | update(param: DriverEntity): Observable { 44 | return this.http 45 | .put(environment.serverUrl + '/motoristas/' + param.id, param) 46 | .pipe(map(this.mapper.mapFrom)); 47 | } 48 | disableEnable(id: number, status: boolean): Observable { 49 | return this.http 50 | .patch(environment.serverUrl + '/motoristas/' + id, { enable: status }) 51 | .pipe(map(this.mapper.mapFrom)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/iusuario-request-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { IUsuarioRequestEntity } from './iusuario-request-entity'; 2 | 3 | describe('IUsuarioRequestEntity', () => { 4 | it('deve ser criado uma instancia', () => { 5 | expect(new IUsuarioRequestEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/iusuario-request-entity.ts: -------------------------------------------------------------------------------- 1 | export class IUsuarioRequestEntity { 2 | username: string = null; 3 | password: string = null; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/iusuario-response-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { IUsuarioResponseEntity } from './iusuario-response-entity'; 2 | 3 | describe('IUsuarioResponseEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new IUsuarioResponseEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/iusuario-response-entity.ts: -------------------------------------------------------------------------------- 1 | export class IUsuarioResponseEntity { 2 | id: number = null; 3 | nome: string = null; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/usuario-mapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { UsuarioMapper } from './usuario-mapper'; 2 | 3 | describe('UsuarioMapper', () => { 4 | xit('should create an instance', () => { 5 | expect(new UsuarioMapper()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/usuario-mapper.ts: -------------------------------------------------------------------------------- 1 | import * as automapper from 'automapper-ts'; 2 | 3 | import { Mapper } from '../../base/mapper'; 4 | import { IUsuarioRequestEntity } from './iusuario-request-entity'; 5 | import { IUsuarioResponseEntity } from './iusuario-response-entity'; 6 | import { UserEntity } from '../../../domain/entities/user-entity'; 7 | export class UsuarioMapper implements Mapper { 8 | mapFrom(param: UserEntity): UserEntity { 9 | automapper.createMap('UserEntity', UserEntity); 10 | 11 | return automapper.map('UserEntity', UserEntity, param); 12 | } 13 | 14 | mapTo(param: UserEntity): UserEntity { 15 | automapper 16 | .createMap('UserEntity', UserEntity); 17 | 18 | return automapper.map('UserEntity', UserEntity, param); 19 | } 20 | 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/usuario-repository.spec.ts: -------------------------------------------------------------------------------- 1 | import { UsuarioRepository } from './usuario-repository'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 4 | import { environment } from '../../../../environments/environment'; 5 | 6 | describe('data: UsuarioRepository', () => { 7 | let service: UsuarioRepository; 8 | let backend: HttpTestingController; 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | HttpClientTestingModule, 14 | ], 15 | providers: [UsuarioRepository] 16 | }).compileComponents(); 17 | 18 | service = TestBed.get(UsuarioRepository); 19 | backend = TestBed.get(HttpTestingController); 20 | }); 21 | 22 | it('deve criar uma instancia', () => { 23 | expect(service).toBeTruthy(); 24 | }); 25 | 26 | describe('quando fizer o login', () => { 27 | it('deve fazer uma solicitação GET', async(() => { 28 | const user = { username: 'test', password: '123' }; 29 | 30 | service.login(user).subscribe(() => {}); 31 | 32 | const req = backend.expectOne(environment.serverUrl + '/usuarios?username=' + user.username + '&password=' + user.password + ''); 33 | expect(req.request.method).toBe('GET'); 34 | 35 | backend.verify(); 36 | })); 37 | 38 | it('deve retornar um usuario', () => { 39 | const user = { username: 'test', password: '123' }; 40 | 41 | service.login(user).subscribe((item) => { 42 | if (item) { 43 | expect(item).toBeTruthy(); 44 | } 45 | }); 46 | 47 | const req = backend.expectOne(environment.serverUrl + '/usuarios?username=' + user.username + '&password=' + user.password + ''); 48 | 49 | req.flush([{ 50 | id: 1 51 | }]); 52 | 53 | backend.verify(); 54 | }); 55 | 56 | it('deve retornar null ao executar o metodo login', () => { 57 | const param = { 58 | username: 'usuario1', 59 | password: '123' 60 | }; 61 | 62 | service.login(param).subscribe(res => { 63 | expect(res).toEqual(null); 64 | }); 65 | 66 | const req = backend.expectOne(environment.serverUrl + '/usuarios?username=' + param.username + '&password=' + param.password + ''); 67 | 68 | req.flush([null]); 69 | }); 70 | }); 71 | 72 | describe('quando fizer o logout', () => { 73 | it('deve deslogar o usuario', () => { 74 | expect(service.logout()).toBeTruthy(); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/app/data/repository/usuario/usuario-repository.ts: -------------------------------------------------------------------------------- 1 | import { Observable, of } from 'rxjs'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from 'src/environments/environment'; 4 | import { map } from 'rxjs/operators'; 5 | import { UsuarioMapper } from './usuario-mapper'; 6 | import { UserEntity } from '../../../domain/entities/user-entity'; 7 | import { IUsuarioRepository } from 'src/app/domain/interfaces/repository/iusuario-repository'; 8 | 9 | export class UsuarioRepository implements IUsuarioRepository { 10 | 11 | private mapper = new UsuarioMapper(); 12 | 13 | constructor( 14 | private http: HttpClient 15 | ) { } 16 | 17 | login(param: UserEntity): Observable { 18 | const usuario = this.mapper.mapTo(param); 19 | 20 | return this.http 21 | .get(environment.serverUrl + '/usuarios?username=' + usuario.username + '&password=' + usuario.password + '') 22 | .pipe(map((item) => { 23 | if (item[0]) { 24 | return this.mapper.mapFrom(item[0]); 25 | } 26 | 27 | return null; 28 | })); 29 | } 30 | 31 | logout(): Observable { 32 | return of(true); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/app/domain/domain.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { IValidatorMessage } from './interfaces/message/ivalidator-message'; 5 | import { ValidatorMessageService } from './message/validator-message.service'; 6 | import { IUsuarioUseCase } from './interfaces/usecases/iusuario-use-case'; 7 | import { UsuarioUseCase } from './usecases/usuario/usuario-use-case'; 8 | import { IMotoristaUsecase } from './interfaces/usecases/imotorista-usecase'; 9 | import { MotoristaUsecaseService } from './usecases/motorista/motorista-usecase.service'; 10 | import { IUsuarioValidator } from './interfaces/validations/iusuario-validator'; 11 | import { UsuarioValidatorService } from './validations/usuario/usuario-validator.service'; 12 | import { IMotoristaValidator } from './interfaces/validations/imotorista-validator'; 13 | import { MotoristaValidatorService } from './validations/motorista/motorista-validator.service'; 14 | 15 | @NgModule({ 16 | declarations: [], 17 | imports: [ 18 | CommonModule 19 | ], 20 | providers: [ 21 | { provide: IValidatorMessage, useClass: ValidatorMessageService }, 22 | 23 | { provide: IUsuarioUseCase, useClass: UsuarioUseCase }, 24 | { provide: IUsuarioValidator, useClass: UsuarioValidatorService }, 25 | 26 | { provide: IMotoristaUsecase, useClass: MotoristaUsecaseService }, 27 | { provide: IMotoristaValidator, useClass: MotoristaValidatorService }, 28 | ], 29 | }) 30 | export class DomainModule { } 31 | -------------------------------------------------------------------------------- /src/app/domain/entities/addresses-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { AddressesEntity } from './addresses-entity'; 2 | 3 | describe('AddressesEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new AddressesEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/domain/entities/addresses-entity.ts: -------------------------------------------------------------------------------- 1 | import { DomainEntity } from './base/domain-entity'; 2 | export class AddressesEntity extends DomainEntity { 3 | name: string = null; 4 | state: string = null; 5 | country: string = null; 6 | neighborhood: string = null; 7 | city: string = null; 8 | street_number: number = null; 9 | complement: string = null; 10 | postal_code: string = null; 11 | street_name: string = null; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/domain/entities/base/domain-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { DomainEntity } from './domain-entity'; 2 | 3 | describe('DomainEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new DomainEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/domain/entities/base/domain-entity.ts: -------------------------------------------------------------------------------- 1 | export class DomainEntity { 2 | id?: number = null; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/domain/entities/documents-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { DocumentsEntity } from './documents-entity'; 2 | 3 | describe('DocumentsEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new DocumentsEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/domain/entities/documents-entity.ts: -------------------------------------------------------------------------------- 1 | import { DomainEntity } from './base/domain-entity'; 2 | export class DocumentsEntity extends DomainEntity { 3 | expires_at: Date = null; 4 | country: string = null; 5 | number: string = null; 6 | doc_type: string = null; 7 | category: string = null; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/domain/entities/driver-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { DriverEntity } from './driver-entity'; 2 | 3 | describe('DriverEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new DriverEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/domain/entities/driver-entity.ts: -------------------------------------------------------------------------------- 1 | import { DomainEntity } from './base/domain-entity'; 2 | import { AddressesEntity } from './addresses-entity'; 3 | import { DocumentsEntity } from './documents-entity'; 4 | export class DriverEntity extends DomainEntity { 5 | name: string = ''; 6 | birth_date: Date = null; 7 | phone: string = ''; 8 | state: string = ''; 9 | city: string = ''; 10 | enable: boolean = null; 11 | addresses: AddressesEntity = null; 12 | documents: DocumentsEntity[] = null; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/domain/entities/user-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from './user-entity'; 2 | 3 | describe('UserEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new UserEntity()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/domain/entities/user-entity.ts: -------------------------------------------------------------------------------- 1 | import { DomainEntity } from './base/domain-entity'; 2 | export class UserEntity extends DomainEntity { 3 | username?: string = null; 4 | password?: string = null; 5 | email?: string = null; 6 | token?: string = null; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/controllers/imotorista-controller.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { DriverEntity } from '../../entities/driver-entity'; 3 | 4 | export abstract class IMotoristaController { 5 | abstract get(id?: number): Observable; 6 | abstract insert(param: DriverEntity): Observable; 7 | abstract update(param: DriverEntity): Observable; 8 | abstract disableEnable(id: number, status: boolean): Observable; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/controllers/iusuario-controller.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { UserEntity } from '../../entities/user-entity'; 3 | 4 | export abstract class IUsuarioController { 5 | abstract login(param: UserEntity): Observable; 6 | abstract logout(): Observable; 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/entity/domain-entity.ts: -------------------------------------------------------------------------------- 1 | export interface DomainEntity { 2 | id?: number; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/message/ivalidator-message.ts: -------------------------------------------------------------------------------- 1 | export abstract class IValidatorMessage { 2 | abstract required(field: string): any; 3 | abstract maximumSize(field: string, characters: string): any; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/repository/imotorista-repository.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { DriverEntity } from '../../entities/driver-entity'; 3 | 4 | export abstract class IMotoristaRepository { 5 | abstract get(id?: number): Observable; 6 | abstract insert(param: DriverEntity): Observable; 7 | abstract update(param: DriverEntity): Observable; 8 | abstract disableEnable(id: number, status: boolean): Observable; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/repository/iusuario-repository.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { UserEntity } from '../../entities/user-entity'; 3 | 4 | export abstract class IUsuarioRepository { 5 | abstract login(param: UserEntity): Observable; 6 | abstract logout(): Observable; 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/usecases/imotorista-usecase.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { DriverEntity } from '../../entities/driver-entity'; 3 | 4 | export abstract class IMotoristaUsecase { 5 | abstract get(id?: number): Observable; 6 | abstract insert(param: DriverEntity): Observable; 7 | abstract update(param: DriverEntity): Observable; 8 | abstract disableEnable(id: number, status: boolean): Observable; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/usecases/iusuario-use-case.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { UserEntity } from '../../entities/user-entity'; 3 | 4 | export abstract class IUsuarioUseCase { 5 | abstract login(param: UserEntity): Observable; 6 | abstract logout(): Observable; 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/validations/imotorista-validator.ts: -------------------------------------------------------------------------------- 1 | import { ValidationResult } from 'ts.validator.fluent/dist'; 2 | import { DriverEntity } from '../../entities/driver-entity'; 3 | 4 | export abstract class IMotoristaValidator { 5 | abstract validateFields(param: DriverEntity): ValidationResult; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/domain/interfaces/validations/iusuario-validator.ts: -------------------------------------------------------------------------------- 1 | import { ValidationResult } from 'ts.validator.fluent/dist'; 2 | import { UserEntity } from '../../entities/user-entity'; 3 | 4 | export abstract class IUsuarioValidator { 5 | abstract validateFields(param: UserEntity): ValidationResult; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/domain/message/validator-message.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ValidatorMessageService } from './validator-message.service'; 4 | import { TranslateService } from '@ngx-translate/core'; 5 | 6 | describe('ValidatorMessageService:', () => { 7 | let validatorMessageService: ValidatorMessageService; 8 | let translate: TranslateService; 9 | 10 | beforeEach(() => TestBed.configureTestingModule({ 11 | providers: [ 12 | TranslateService 13 | ] 14 | })); 15 | 16 | beforeEach(() => { 17 | validatorMessageService = TestBed.get(ValidatorMessageService); 18 | translate = TestBed.get(TranslateService); 19 | }); 20 | 21 | xit('deve ser criado', () => { 22 | const service: ValidatorMessageService = TestBed.get(ValidatorMessageService); 23 | expect(service).toBeTruthy(); 24 | }); 25 | 26 | xit('deve retornar um texto do tipo required', () => { 27 | validatorMessageService.required('Usuário'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/domain/message/validator-message.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { IValidatorMessage } from '../interfaces/message/ivalidator-message'; 3 | import { TranslateService } from '@ngx-translate/core'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class ValidatorMessageService implements IValidatorMessage { 9 | 10 | constructor( 11 | private translate: TranslateService 12 | ) { } 13 | 14 | required(field: string) { 15 | return this.translate.get('validateRequired', { 16 | 0: field 17 | }); 18 | } 19 | maximumSize(field: string, characters: string) { 20 | return this.translate.get('validateMaximumSize', { 21 | 0: field, 1: characters 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/domain/usecases/motorista/motorista-usecase.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MotoristaUsecaseService } from './motorista-usecase.service'; 4 | 5 | describe('MotoristaUsecaseService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | xit('should be created', () => { 9 | const service: MotoristaUsecaseService = TestBed.get(MotoristaUsecaseService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/domain/usecases/motorista/motorista-usecase.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, throwError } from 'rxjs'; 3 | 4 | import { IMotoristaUsecase } from '../../interfaces/usecases/imotorista-usecase'; 5 | import { IMotoristaRepository } from '../../interfaces/repository/imotorista-repository'; 6 | import { IMotoristaValidator } from '../../interfaces/validations/imotorista-validator'; 7 | import { DriverEntity } from '../../entities/driver-entity'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class MotoristaUsecaseService implements IMotoristaUsecase { 13 | 14 | constructor( 15 | private motoristaRepository: IMotoristaRepository, 16 | private motoristaValidator: IMotoristaValidator 17 | ) { } 18 | 19 | get(id?: number): Observable { 20 | if (id) { 21 | return this.motoristaRepository.get(id); 22 | } else { 23 | return this.motoristaRepository.get(); 24 | } 25 | } 26 | insert(param: DriverEntity): Observable { 27 | const validator = this.motoristaValidator.validateFields(param); 28 | 29 | if (validator.IsValid) { 30 | return this.motoristaRepository.insert(param); 31 | } else { 32 | return throwError(validator.Errors); 33 | } 34 | } 35 | update(param: DriverEntity): Observable { 36 | const validator = this.motoristaValidator.validateFields(param); 37 | 38 | if (validator.IsValid) { 39 | return this.motoristaRepository.update(param); 40 | } else { 41 | return throwError(validator.Errors); 42 | } 43 | } 44 | disableEnable(id: number, status: boolean): Observable { 45 | return this.motoristaRepository.disableEnable(id, status); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/app/domain/usecases/usuario/usuario-use-case.spec.ts: -------------------------------------------------------------------------------- 1 | import { UsuarioUseCase } from './usuario-use-case'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { IUsuarioRepository } from '../../interfaces/repository/iusuario-repository'; 4 | import { IUsuarioValidator } from '../../interfaces/validations/iusuario-validator'; 5 | 6 | describe('UsuarioUseCase', () => { 7 | let usuarioUseCase: UsuarioUseCase; 8 | let usuarioRepository: jasmine.SpyObj; 9 | let usuarioValidator: jasmine.SpyObj; 10 | 11 | beforeEach(() => { 12 | const repositorySpy = jasmine.createSpyObj('IUsuarioRepository', ['login', 'logout']); 13 | const validationSpy = jasmine.createSpyObj('IUsuarioValidator', ['validateFields']); 14 | 15 | TestBed.configureTestingModule({ 16 | providers: [ 17 | { provide: IUsuarioRepository, useValue: repositorySpy }, 18 | { provide: IUsuarioValidator, useValue: validationSpy } 19 | ] 20 | }) 21 | .compileComponents(); 22 | 23 | usuarioUseCase = TestBed.get(UsuarioUseCase); 24 | usuarioRepository = TestBed.get(IUsuarioRepository); 25 | usuarioValidator = TestBed.get(IUsuarioValidator); 26 | }); 27 | 28 | it('deve ser criado', () => { 29 | expect(usuarioUseCase).toBeTruthy(); 30 | }); 31 | 32 | xit('deve executar o metodo login e ser valido', () => { 33 | const mock = { 34 | username: 'test', 35 | password: '123' 36 | }; 37 | 38 | usuarioUseCase.login(mock); 39 | 40 | // expect(validationResult..IsValid).toBeFalsy(); 41 | 42 | // spyOn(usuarioValidator, 'validateFields').and.returnValue(true); 43 | 44 | // expect(usuarioValidator.validateFields(mock)).toBeTruthy(); 45 | }); 46 | 47 | xit('deve executar o metodo login e ser invalido', () => { 48 | const mock = { 49 | username: '', 50 | password: '' 51 | }; 52 | 53 | usuarioUseCase.login(mock); 54 | 55 | expect(usuarioValidator.validateFields(mock)).toBeFalsy(); 56 | }); 57 | 58 | it('deve executar o metodo logout', () => { 59 | usuarioUseCase.logout(); 60 | expect(usuarioRepository.logout.calls.count()).toBe(1); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/app/domain/usecases/usuario/usuario-use-case.ts: -------------------------------------------------------------------------------- 1 | import { Observable, throwError } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | import { IUsuarioUseCase } from '../../interfaces/usecases/iusuario-use-case'; 5 | import { IUsuarioRepository } from '../../interfaces/repository/iusuario-repository'; 6 | import { IUsuarioValidator } from '../../interfaces/validations/iusuario-validator'; 7 | import { UserEntity } from '../../entities/user-entity'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class UsuarioUseCase implements IUsuarioUseCase { 13 | 14 | constructor( 15 | private usuarioRepository: IUsuarioRepository, 16 | private usuarioValidator: IUsuarioValidator 17 | ) { } 18 | 19 | login(param: UserEntity): Observable { 20 | const validator = this.usuarioValidator.validateFields(param); 21 | 22 | if (validator.IsValid) { 23 | return this.usuarioRepository.login(param); 24 | } else { 25 | return throwError(validator.Errors); 26 | } 27 | } 28 | 29 | logout(): Observable { 30 | return this.usuarioRepository.logout(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/app/domain/validations/motorista/motorista-validator.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MotoristaValidatorService } from './motorista-validator.service'; 4 | 5 | describe('MotoristaValidatorService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | xit('should be created', () => { 9 | const service: MotoristaValidatorService = TestBed.get(MotoristaValidatorService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/domain/validations/motorista/motorista-validator.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ValidationResult, Validator, IValidator } from 'ts.validator.fluent/dist'; 3 | 4 | import { IMotoristaValidator } from '../../interfaces/validations/imotorista-validator'; 5 | import { IValidatorMessage } from '../../interfaces/message/ivalidator-message'; 6 | import { DriverEntity } from '../../entities/driver-entity'; 7 | import { DocumentsEntity } from '../../entities/documents-entity'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class MotoristaValidatorService implements IMotoristaValidator { 13 | 14 | constructor( 15 | protected validatorMessage: IValidatorMessage 16 | ) { } 17 | 18 | validateFields(param: DriverEntity): ValidationResult { 19 | return new Validator(param).Validate(this.validateRules); 20 | } 21 | 22 | validateRules = (validator: IValidator): ValidationResult => { 23 | return validator 24 | .NotEmpty(m => m.name, this.validatorMessage.required('Nome').value) 25 | .NotEmpty(m => m.birth_date.toString(), this.validatorMessage.required('Data de Nascimento').value) 26 | .If(m => m.documents != null && m.documents.length > 0, 27 | v => v.ForEach(m => m.documents, this.validateDocumentsRules).ToResult()) 28 | .ToResult(); 29 | } 30 | 31 | validateDocumentsRules = (validator: IValidator): ValidationResult => { 32 | return validator 33 | .NotEmpty(m => m.doc_type, this.validatorMessage.required('Tipo de documento').value) 34 | .If(m => m.doc_type !== '', (v: IValidator): ValidationResult => { 35 | return v 36 | .NotEmpty(m => m.number, this.validatorMessage.required('Número de documento').value) 37 | .If(m => m.doc_type === 'CNH', 38 | c => c.NotEmpty(m => m.category, this.validatorMessage.required('Categoria da CNH').value).ToResult()) 39 | .ToResult(); 40 | }) 41 | .ToResult(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/app/domain/validations/usuario/usuario-validator.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UsuarioValidatorService } from './usuario-validator.service'; 4 | 5 | describe('UsuarioValidatorService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | xit('should be created', () => { 9 | const service: UsuarioValidatorService = TestBed.get(UsuarioValidatorService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/domain/validations/usuario/usuario-validator.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ValidationResult, Validator, IValidator } from 'ts.validator.fluent/dist'; 3 | 4 | import { IUsuarioValidator } from '../../interfaces/validations/iusuario-validator'; 5 | import { IValidatorMessage } from '../../interfaces/message/ivalidator-message'; 6 | import { UserEntity } from '../../entities/user-entity'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class UsuarioValidatorService implements IUsuarioValidator { 12 | 13 | constructor( 14 | protected validatorMessage: IValidatorMessage 15 | ) { } 16 | 17 | validateFields(param: UserEntity): ValidationResult { 18 | return new Validator(param).Validate(this.validateRules); 19 | } 20 | 21 | validateRules = (validator: IValidator): ValidationResult => { 22 | return validator 23 | .NotEmpty(m => m.username, this.validatorMessage.required('Usuário').value) 24 | .Length(m => m.username, 0, 10, this.validatorMessage.maximumSize('Usuário', '10').value) 25 | .NotEmpty(m => m.password, this.validatorMessage.required('Senha').value) 26 | .Length(m => m.password, 0, 10, this.validatorMessage.maximumSize('Senha', '10').value) 27 | .ToResult(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/infra/auth/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { AuthGuard } from './auth.guard'; 4 | import { AuthService } from './auth.service'; 5 | 6 | class MockRouter { 7 | navigateByUrl(path) {} 8 | } 9 | 10 | describe('AuthGuard', () => { 11 | let authGuard: AuthGuard; 12 | let authService; 13 | let router; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | providers: [AuthGuard] 18 | }); 19 | }); 20 | 21 | describe('canActivate', () => { 22 | it('deve retornar true para um usuário logado', () => { 23 | authService = { isAuthenticated: () => true }; 24 | router = new MockRouter(); 25 | authGuard = new AuthGuard(router, authService); 26 | 27 | expect(authGuard.canActivate()).toEqual(true); 28 | }); 29 | 30 | it('deve navegar para o login um usuário desconectado', () => { 31 | authService = { isAuthenticated: () => false }; 32 | router = new MockRouter(); 33 | authGuard = new AuthGuard(router, authService); 34 | 35 | spyOn(router, 'navigateByUrl'); 36 | 37 | expect(authGuard.canActivate()).toEqual(false); 38 | expect(router.navigateByUrl).toHaveBeenCalledWith('/login', { replaceUrl: true }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/infra/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AuthGuard implements CanActivate { 11 | 12 | constructor( 13 | private router: Router, 14 | private authService: AuthService, 15 | ) { } 16 | 17 | canActivate(): Observable | Promise | boolean | UrlTree { 18 | if (this.authService.isAuthenticated()) { 19 | return true; 20 | } 21 | 22 | this.router.navigateByUrl('/login', { replaceUrl: true }); 23 | return false; 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/app/infra/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | import { UsuarioModel } from '../../core/domain/entity/usuario-model'; 5 | 6 | const credentialsKey = 'credentials'; 7 | 8 | describe('AuthService', () => { 9 | let authService: AuthService; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | providers: [AuthService] 14 | }); 15 | 16 | authService = TestBed.get(AuthService); 17 | }); 18 | 19 | afterEach(() => { 20 | localStorage.removeItem(credentialsKey); 21 | sessionStorage.removeItem(credentialsKey); 22 | }); 23 | 24 | it('deve ser criado', () => { 25 | expect(authService).toBeTruthy(); 26 | }); 27 | 28 | describe('login', () => { 29 | it('deve retornar credenciais', () => { 30 | const usuario = { 31 | id: 1, 32 | username: 'test', 33 | email: 'test@test.com.br', 34 | token: '123' 35 | }; 36 | 37 | authService.credentials = usuario; 38 | 39 | expect(authService.credentials).toBeDefined(); 40 | expect(authService.credentials.token).toBeDefined(); 41 | }); 42 | 43 | it('deve autenticar usuário', () => { 44 | const usuario = { 45 | id: 1, 46 | username: 'test', 47 | email: 'test@test.com.br', 48 | token: '123' 49 | }; 50 | 51 | authService.credentials = usuario; 52 | 53 | expect(authService.isAuthenticated()).toBe(true); 54 | }); 55 | }); 56 | 57 | describe('logout', () => { 58 | it('deve limpar a autenticação do usuário', () => { 59 | const usuario = null; 60 | 61 | authService.credentials = usuario; 62 | 63 | expect(authService.isAuthenticated()).toBe(false); 64 | expect(authService.credentials).toBeNull(); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/app/infra/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { UserEntity } from '../../domain/entities/user-entity'; 3 | 4 | const credentialsKey = 'credentials'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthService { 10 | 11 | private usuario: UserEntity; 12 | 13 | constructor() { 14 | const savedCredentials = sessionStorage.getItem(credentialsKey) || localStorage.getItem(credentialsKey); 15 | 16 | if (savedCredentials) { 17 | this.usuario = JSON.parse(savedCredentials); 18 | } 19 | } 20 | 21 | isAuthenticated(): boolean { 22 | return !!this.credentials; 23 | } 24 | 25 | get credentials(): UserEntity { 26 | return this.usuario; 27 | } 28 | 29 | set credentials(credentials: UserEntity) { 30 | this.usuario = credentials || null; 31 | 32 | if (credentials) { 33 | localStorage.setItem(credentialsKey, JSON.stringify(credentials)); 34 | } else { 35 | localStorage.removeItem(credentialsKey); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/infra/http/http-interceptor.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { HttpInterceptorService } from './http-interceptor.service'; 4 | 5 | describe('HttpInterceptorService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: HttpInterceptorService = TestBed.get(HttpInterceptorService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/infra/http/http-interceptor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { AuthService } from '../auth/auth.service'; 5 | import { catchError } from 'rxjs/operators'; 6 | import { Router } from '@angular/router'; 7 | import { Validator, ValidationError } from 'ts.validator.fluent/dist'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class HttpInterceptorService implements HttpInterceptor { 13 | 14 | constructor( 15 | private authService: AuthService, 16 | private router: Router 17 | ) { } 18 | 19 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 20 | if (this.authService.credentials) { 21 | req = req.clone({ 22 | setHeaders: { 23 | Authorization: `Bearer ${this.authService.credentials.token}` 24 | } 25 | }); 26 | 27 | return next.handle(req).pipe(catchError(error => this.errorHandler(error))); 28 | } else { 29 | return next.handle(req).pipe(catchError(error => this.errorHandler(error))); 30 | } 31 | } 32 | 33 | private errorHandler(response: HttpErrorResponse): Observable> { 34 | let errs: any[] = []; 35 | 36 | switch (response.status) { 37 | case 400: 38 | console.log('Error', response.status); 39 | break; 40 | case 401: 41 | this.router.navigateByUrl('/login', { replaceUrl: true }); 42 | break; 43 | case 404: 44 | errs.push(new ValidationError('', '', '404: O recurso requisitado não existe.')); 45 | break; 46 | case 406: 47 | case 409: 48 | case 500: 49 | console.log('Ocorreu um erro inesperado de servidor.'); 50 | break; 51 | } 52 | 53 | return throwError(errs); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/infra/infra.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; 4 | import { TranslateHttpLoader } from '@ngx-translate/http-loader'; 5 | import { HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; 6 | import { HttpInterceptorService } from './http/http-interceptor.service'; 7 | 8 | @NgModule({ 9 | declarations: [], 10 | imports: [ 11 | CommonModule, 12 | TranslateModule.forRoot({ 13 | loader: { 14 | provide: TranslateLoader, 15 | useFactory: (http: HttpClient) => { 16 | return new TranslateHttpLoader(http); 17 | }, 18 | deps: [ HttpClient ] 19 | } 20 | }), 21 | ], 22 | providers: [ 23 | { 24 | provide: HTTP_INTERCEPTORS, 25 | useClass: HttpInterceptorService, 26 | multi: true 27 | } 28 | ] 29 | }) 30 | export class InfraModule { } 31 | -------------------------------------------------------------------------------- /src/app/infra/translations/i18n.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { I18nService } from './i18n.service'; 4 | import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; 5 | import { Subject } from 'rxjs'; 6 | 7 | const defaultLanguage = 'pt-BR'; 8 | const supportedLanguages = ['pt-BR', 'en-US']; 9 | 10 | class MockTranslateService { 11 | 12 | currentLang = ''; 13 | onLangChange = new Subject(); 14 | 15 | use(language: string) { 16 | this.currentLang = language; 17 | this.onLangChange.next({ 18 | lang: this.currentLang, 19 | translations: {} 20 | }); 21 | } 22 | 23 | getBrowserCultureLang() { 24 | return 'pt-BR'; 25 | } 26 | 27 | setTranslation(lang: string, translations: Object, shouldMerge?: boolean) { } 28 | 29 | } 30 | 31 | describe('I18nService', () => { 32 | let i18nService: I18nService; 33 | let translateService: TranslateService; 34 | let onLangChangeSpy: jasmine.Spy; 35 | 36 | beforeEach(() => { 37 | TestBed.configureTestingModule({ 38 | providers: [ 39 | I18nService, 40 | { provide: TranslateService, useClass: MockTranslateService } 41 | ] 42 | }); 43 | 44 | i18nService = TestBed.get(I18nService); 45 | translateService = TestBed.get(TranslateService); 46 | 47 | onLangChangeSpy = jasmine.createSpy('onLangChangeSpy'); 48 | 49 | translateService.onLangChange.subscribe((event: LangChangeEvent) => { 50 | onLangChangeSpy(event.lang); 51 | }); 52 | 53 | spyOn(translateService, 'use').and.callThrough(); 54 | }); 55 | 56 | afterEach(() => { 57 | localStorage.removeItem('language'); 58 | }); 59 | 60 | it('deve ser criado', () => { 61 | const service: I18nService = TestBed.get(I18nService); 62 | expect(service).toBeTruthy(); 63 | }); 64 | 65 | describe('init', () => { 66 | it('deve iniciar com o idioma padrão', () => { 67 | i18nService.init(defaultLanguage, supportedLanguages); 68 | 69 | expect(translateService.use).toHaveBeenCalledWith(defaultLanguage); 70 | expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage); 71 | }); 72 | 73 | it('deve iniciar com salvar idioma', () => { 74 | const savedLanguage = 'pt-BR'; 75 | localStorage.setItem('language', savedLanguage); 76 | 77 | i18nService.init(defaultLanguage, supportedLanguages); 78 | 79 | expect(translateService.use).toHaveBeenCalledWith(savedLanguage); 80 | expect(onLangChangeSpy).toHaveBeenCalledWith(savedLanguage); 81 | }); 82 | }); 83 | 84 | describe('Definir idioma', () => { 85 | it('deve mudar o idioma atual', () => { 86 | const newLanguage = 'en-US'; 87 | i18nService.init(defaultLanguage, supportedLanguages); 88 | 89 | i18nService.language = newLanguage; 90 | 91 | expect(translateService.use).toHaveBeenCalledWith(newLanguage); 92 | expect(onLangChangeSpy).toHaveBeenCalledWith(newLanguage); 93 | }); 94 | 95 | it('deve alterar o idioma atual para o padrão, se não for suportado', () => { 96 | const newLanguage = 'es'; 97 | i18nService.init(defaultLanguage, supportedLanguages); 98 | 99 | i18nService.language = newLanguage; 100 | 101 | expect(translateService.use).toHaveBeenCalledWith(defaultLanguage); 102 | expect(onLangChangeSpy).toHaveBeenCalledWith(defaultLanguage); 103 | }); 104 | }); 105 | 106 | describe('obter linguagem', () => { 107 | it('deve retornar o idioma atual', () => { 108 | i18nService.init(defaultLanguage, supportedLanguages); 109 | 110 | const currentLanguage = i18nService.language; 111 | 112 | expect(currentLanguage).toEqual(defaultLanguage); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /src/app/infra/translations/i18n.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; 3 | import { includes } from 'lodash'; 4 | 5 | const languageKey = 'language'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class I18nService { 11 | 12 | defaultLanguage: string; 13 | supportedLanguages: string[]; 14 | 15 | constructor( 16 | private translate: TranslateService 17 | ) { } 18 | 19 | get language(): string { 20 | return this.translate.currentLang; 21 | } 22 | 23 | set language(language: string) { 24 | language = language || localStorage.getItem(languageKey) || this.translate.getBrowserCultureLang(); 25 | 26 | let isSupportedLanguage = includes(this.supportedLanguages, language); 27 | 28 | if (language && !isSupportedLanguage) { 29 | language = language.split('-')[0]; 30 | language = this.supportedLanguages.find(supportedLanguage => supportedLanguage.startsWith(language)) || ''; 31 | isSupportedLanguage = Boolean(language); 32 | } 33 | 34 | if (!isSupportedLanguage) { 35 | language = this.defaultLanguage; 36 | } 37 | 38 | this.translate.use(language); 39 | } 40 | 41 | init(defaultLanguage: string, supportedLanguages: string[]) { 42 | this.defaultLanguage = defaultLanguage; 43 | this.supportedLanguages = supportedLanguages; 44 | this.language = ''; 45 | 46 | this.translate.onLangChange 47 | .subscribe((event: LangChangeEvent) => { 48 | localStorage.setItem(languageKey, event.lang); 49 | }); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/app/presentation/controllers/motorista/motorista-controller.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MotoristaControllerService } from './motorista-controller.service'; 4 | import { IMotoristaUsecase } from '../../../core/interfaces/usecases/imotorista-usecase'; 5 | import { MotoristaModel } from '../../../core/domain/entity/motorista-model'; 6 | 7 | describe('MotoristaControllerService:', () => { 8 | let motoristaController: MotoristaControllerService; 9 | let motoristaUseCase: jasmine.SpyObj; 10 | 11 | beforeEach(() => { 12 | const spy = jasmine.createSpyObj('IUsuarioUseCase', ['get', 'insert', 'update', 'disableEnable']); 13 | 14 | TestBed.configureTestingModule({ 15 | providers: [ 16 | { provide: IMotoristaUsecase, useValue: spy } 17 | ] 18 | }); 19 | 20 | motoristaUseCase = TestBed.get(IMotoristaUsecase); 21 | motoristaController = TestBed.get(MotoristaControllerService); 22 | }); 23 | 24 | it('deve ser criado', () => { 25 | expect(motoristaController).toBeTruthy(); 26 | }); 27 | 28 | it('deve chamar o metodo get() passando um ID', () => { 29 | const id = 1; 30 | 31 | motoristaController.get(id); 32 | 33 | expect(motoristaUseCase.get.calls.count()).toBe(1); 34 | }); 35 | 36 | it('deve chamar o metodo get() sem passar um ID', () => { 37 | motoristaController.get(); 38 | 39 | expect(motoristaUseCase.get.calls.count()).toBe(1); 40 | }); 41 | 42 | it('deve chamar o metodo insert()', () => { 43 | const motorista = new MotoristaModel(); 44 | 45 | motoristaController.insert(motorista); 46 | 47 | expect(motoristaUseCase.insert.calls.count()).toBe(1); 48 | }); 49 | 50 | it('deve chamar o metodo update()', () => { 51 | const motorista = new MotoristaModel(); 52 | 53 | motoristaController.update(motorista); 54 | 55 | expect(motoristaUseCase.update.calls.count()).toBe(1); 56 | }); 57 | 58 | it('deve chamar o metodo disableEnable()', () => { 59 | const id = 1; 60 | const status = true; 61 | 62 | motoristaController.disableEnable(id, status); 63 | 64 | expect(motoristaUseCase.disableEnable.calls.count()).toBe(1); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/app/presentation/controllers/motorista/motorista-controller.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { IMotoristaController } from 'src/app/domain/interfaces/controllers/imotorista-controller'; 4 | import { IMotoristaUsecase } from 'src/app/domain/interfaces/usecases/imotorista-usecase'; 5 | import { DriverEntity } from '../../../domain/entities/driver-entity'; 6 | 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class MotoristaControllerService implements IMotoristaController { 12 | 13 | constructor( 14 | private motoristaUsecase: IMotoristaUsecase 15 | ) { } 16 | 17 | get(id?: number): Observable { 18 | if (id) { 19 | return this.motoristaUsecase.get(id); 20 | } else { 21 | return this.motoristaUsecase.get(); 22 | } 23 | } 24 | insert(param: DriverEntity): Observable { 25 | return this.motoristaUsecase.insert(param); 26 | } 27 | update(param: DriverEntity): Observable { 28 | return this.motoristaUsecase.update(param); 29 | } 30 | disableEnable(id: number, status: boolean): Observable { 31 | return this.motoristaUsecase.disableEnable(id, status); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/app/presentation/controllers/usuario/usuario-controller.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UsuarioControllerService } from './usuario-controller.service'; 4 | import { IUsuarioUseCase } from 'src/app/core/interfaces/usecases/iusuario-use-case'; 5 | 6 | describe('UsuarioControllerService', () => { 7 | let usuarioController: UsuarioControllerService; 8 | let usuarioUseCase: jasmine.SpyObj; 9 | 10 | beforeEach(() => { 11 | const spy = jasmine.createSpyObj('IUsuarioUseCase', ['login', 'logout']); 12 | 13 | TestBed.configureTestingModule({ 14 | providers: [ 15 | { provide: IUsuarioUseCase, useValue: spy } 16 | ] 17 | }); 18 | 19 | usuarioController = TestBed.get(UsuarioControllerService); 20 | usuarioUseCase = TestBed.get(IUsuarioUseCase); 21 | }); 22 | 23 | it('deve ser criado', () => { 24 | expect(usuarioController).toBeTruthy(); 25 | }); 26 | 27 | it('deve chamar o metodo login', () => { 28 | const mock = { 29 | username: 'test', 30 | password: '123' 31 | }; 32 | 33 | usuarioController.login(mock); 34 | 35 | expect(usuarioUseCase.login.calls.count()).toBe(1); 36 | }); 37 | 38 | it('deve chamar o metodo logout', () => { 39 | usuarioController.logout(); 40 | 41 | expect(usuarioUseCase.logout.calls.count()).toBe(1); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/app/presentation/controllers/usuario/usuario-controller.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { IUsuarioController } from 'src/app/domain/interfaces/controllers/iusuario-controller'; 4 | import { IUsuarioUseCase } from 'src/app/domain/interfaces/usecases/iusuario-use-case'; 5 | import { UserEntity } from '../../../domain/entities/user-entity'; 6 | 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class UsuarioControllerService implements IUsuarioController { 12 | 13 | constructor( 14 | private usuarioUseCase: IUsuarioUseCase 15 | ) { } 16 | 17 | 18 | login(param: UserEntity): Observable { 19 | return this.usuarioUseCase.login(param); 20 | } 21 | 22 | logout(): Observable { 23 | return this.usuarioUseCase.logout(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/presentation/presentation.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { ViewModule } from './view/view.module'; 5 | import { UsuarioControllerService } from './controllers/usuario/usuario-controller.service'; 6 | import { MotoristaControllerService } from './controllers/motorista/motorista-controller.service'; 7 | import { IUsuarioController } from '../domain/interfaces/controllers/iusuario-controller'; 8 | import { IMotoristaController } from '../domain/interfaces/controllers/imotorista-controller'; 9 | 10 | @NgModule({ 11 | declarations: [], 12 | imports: [ 13 | CommonModule, 14 | ViewModule 15 | ], 16 | exports: [ViewModule], 17 | providers: [ 18 | { provide: IUsuarioController, useClass: UsuarioControllerService }, 19 | { provide: IMotoristaController, useClass: MotoristaControllerService }, 20 | ] 21 | }) 22 | export class PresentationModule { } 23 | -------------------------------------------------------------------------------- /src/app/presentation/view/base/base.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ 'TXT_MOTORISTAS' | translate }} 4 | 5 | account_circle 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{ 'TXT_OLA' | translate }}, {{usuario.username}} 15 | {{usuario.email}} 16 | 17 | 18 | 19 | {{ 'TXT_SAIR' | translate }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/app/presentation/view/base/base.component.scss: -------------------------------------------------------------------------------- 1 | .menu-card { 2 | max-width: 400px; 3 | 4 | .mat-card-header { 5 | text-overflow: ellipsis; 6 | overflow: hidden; 7 | } 8 | } 9 | 10 | .menu-card-header-image { 11 | background-image: url('src/assets/images/img-avatar.jpg'); 12 | background-size: cover; 13 | background-color: #ccc; 14 | } 15 | 16 | ::ng-deep { 17 | .menu-user { 18 | .mat-menu-content:not(:empty) { 19 | padding: 0; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/presentation/view/base/base.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BaseComponent } from './base.component'; 4 | import { AppMaterialModule } from 'src/app/app-material.module'; 5 | import { RouterModule, Router } from '@angular/router'; 6 | import { IUsuarioController } from 'src/app/core/interfaces/controllers/iusuario-controller'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | import { UsuarioModel } from 'src/app/core/domain/entity/usuario-model'; 9 | import { Observable, of } from 'rxjs'; 10 | import { TranslateModule } from '@ngx-translate/core'; 11 | 12 | class MockUsuarioController { 13 | get(): UsuarioModel { 14 | return { 15 | id: 1, 16 | username: 'test', 17 | email: 'test@teste.com.br', 18 | token: '123' 19 | }; 20 | } 21 | 22 | logout(): Observable { 23 | return of(true); 24 | } 25 | } 26 | 27 | describe('BaseComponent', () => { 28 | let component: BaseComponent; 29 | let fixture: ComponentFixture; 30 | let usuarioController: MockUsuarioController; 31 | 32 | beforeEach(async(() => { 33 | TestBed.configureTestingModule({ 34 | imports: [ 35 | TranslateModule.forRoot(), 36 | AppMaterialModule, 37 | RouterTestingModule 38 | ], 39 | providers: [ 40 | { provide: IUsuarioController, useClass: MockUsuarioController } 41 | ], 42 | declarations: [ BaseComponent ] 43 | }) 44 | .compileComponents(); 45 | })); 46 | 47 | beforeEach(() => { 48 | fixture = TestBed.createComponent(BaseComponent); 49 | component = fixture.componentInstance; 50 | fixture.detectChanges(); 51 | }); 52 | 53 | it('deve criar', () => { 54 | expect(component).toBeTruthy(); 55 | }); 56 | 57 | it('deve obter as credenciais', () => { 58 | const mock = { 59 | id: 1, 60 | username: 'test', 61 | email: 'test@teste.com.br', 62 | token: '123' 63 | }; 64 | 65 | usuarioController = new MockUsuarioController(); 66 | 67 | spyOn(usuarioController, 'get').and.returnValue(mock); 68 | 69 | expect(usuarioController.get()).toBeTruthy(); 70 | 71 | // component.usuario = usuarioController.get(); 72 | 73 | // usuarioController = new MockUsuarioController(); 74 | 75 | // component.usuario = usuarioController.get(); 76 | // const user = usuarioController.get(); 77 | }); 78 | 79 | it('deve deslogar usuario', () => { 80 | component.logout(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/app/presentation/view/base/base.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AuthService } from 'src/app/infra/auth/auth.service'; 5 | import { IUsuarioController } from 'src/app/domain/interfaces/controllers/iusuario-controller'; 6 | import { UserEntity } from '../../../domain/entities/user-entity'; 7 | 8 | @Component({ 9 | selector: 'app-base', 10 | templateUrl: './base.component.html', 11 | styleUrls: ['./base.component.scss'] 12 | }) 13 | export class BaseComponent implements OnInit { 14 | 15 | constructor( 16 | private authService: AuthService, 17 | private usuarioController: IUsuarioController, 18 | private router: Router 19 | ) { } 20 | 21 | ngOnInit() { 22 | } 23 | 24 | get usuario(): UserEntity { 25 | return this.authService.credentials; 26 | } 27 | 28 | logout() { 29 | this.usuarioController.logout() 30 | .subscribe(() => this.responseLogout()); 31 | } 32 | 33 | responseLogout() { 34 | this.authService.credentials = null; 35 | this.router.navigateByUrl('/login', { replaceUrl: true }); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/presentation/view/base/base.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | 6 | import { BaseComponent } from './base.component'; 7 | import { AppMaterialModule } from 'src/app/app-material.module'; 8 | 9 | @NgModule({ 10 | declarations: [BaseComponent], 11 | imports: [ 12 | CommonModule, 13 | RouterModule, 14 | TranslateModule, 15 | AppMaterialModule 16 | ] 17 | }) 18 | export class BaseModule { } 19 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { RouteService } from '../route.service'; 5 | import { HomeComponent } from './home.component'; 6 | 7 | const routes: Routes = [ 8 | RouteService.withShell([ 9 | { path: '', redirectTo: '/home', pathMatch: 'full' }, 10 | { 11 | path: 'home', 12 | component: HomeComponent, 13 | data: { 14 | title: 'Home' 15 | } 16 | } 17 | ]) 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forChild(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class HomeRoutingModule { } 25 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ 'TXT_NOVO_MOTORISTA' | translate }} 4 | 5 | 6 | 7 | 8 | 9 | {{ 'TXT_NOME' | translate }} 10 | {{element.name}} 11 | 12 | 13 | 14 | {{ 'TXT_TELEFONE' | translate }} 15 | {{element.phone ? element.phone : '-'}} 16 | 17 | 18 | 19 | {{ 'TXT_DATA_NASCIMENTO' | translate }}: 20 | {{element.birth_date | date : 'dd/MM/yyyy'}} 21 | 22 | 23 | 24 | {{ 'TXT_DOCUMENTO' | translate }} 25 | 26 | 27 | 28 | {{item.doc_type}} 29 | 30 | {{item.number}} 31 | / {{item.category}} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {{ 'TXT_ACOES' | translate }} 40 | 41 | 43 | edit 44 | 45 | 46 | 50 | {{element.enable ? 'check_circle_outline' : 'block'}} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {{item.name}} 63 | {{ 'TXT_DATA_NASCIMENTO' | translate }}: {{item.birth_date | date : 'dd/MM/yyyy'}} 64 | {{ 'TXT_TELEFONE' | translate }}: {{item.phone}} 65 | 66 | 67 | {{ 'TXT_DOCUMENTO' | translate }}: 68 | 69 | 70 | {{doc.doc_type}} 71 | 72 | {{doc.number}} 73 | / {{doc.category}} 74 | 75 | 76 | 77 | 78 | 79 | 81 | edit {{ 'TXT_EDITAR' | translate }} 82 | 83 | 84 | 85 | {{item.enable ? 'check_circle_outline' : 'block'}} 86 | {{item.enable ? 'Ativar' : 'Inativar'}} 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home.component.scss: -------------------------------------------------------------------------------- 1 | table { 2 | width: 100%; 3 | } 4 | 5 | ::ng-deep { 6 | .mat-column-action { 7 | width: 120px; 8 | } 9 | 10 | .card-list { 11 | margin-bottom: 16px; 12 | 13 | .mat-card-header-text { 14 | margin: 0; 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import { AppMaterialModule } from 'src/app/app-material.module'; 5 | import { IMotoristaController } from 'src/app/core/interfaces/controllers/imotorista-controller'; 6 | import { of, Observable } from 'rxjs'; 7 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 8 | import { SharedModule } from '../../shared/shared.module'; 9 | import { MatDialog, MatDialogRef } from '@angular/material'; 10 | import { DialogCadastroComponent } from '../../shared/dialogs/dialog-cadastro/dialog-cadastro.component'; 11 | import { TranslateModule } from '@ngx-translate/core'; 12 | import { MotoristaModel } from 'src/app/core/domain/entity/motorista-model'; 13 | 14 | class MatDialogMock { 15 | // When the component calls this.dialog.open(...) we'll return an object 16 | // with an afterClosed method that allows to subscribe to the dialog result observable. 17 | open() { 18 | return { 19 | afterClosed: () => of(true) 20 | }; 21 | } 22 | } 23 | 24 | describe('HomeComponent', () => { 25 | let component: HomeComponent; 26 | let fixture: ComponentFixture; 27 | let motoristaController: jasmine.SpyObj; 28 | let dialog: MatDialogMock; 29 | 30 | beforeEach(async(() => { 31 | const controllerSpy = jasmine.createSpyObj('IUsuarioController', ['get', 'insert', 'update', 'disableEnable']); 32 | 33 | TestBed.configureTestingModule({ 34 | declarations: [ HomeComponent ], 35 | imports: [ 36 | AppMaterialModule, 37 | BrowserAnimationsModule, 38 | TranslateModule.forRoot(), 39 | SharedModule 40 | ], 41 | providers: [ 42 | { provide: IMotoristaController, useValue: controllerSpy }, 43 | { provide: MatDialog, useClass: MatDialogMock } 44 | ] 45 | }) 46 | .compileComponents() 47 | .then(() => { 48 | fixture = TestBed.createComponent(HomeComponent); 49 | component = fixture.componentInstance; 50 | motoristaController = TestBed.get(IMotoristaController); 51 | dialog = TestBed.get(MatDialog); 52 | }); 53 | })); 54 | 55 | it('deve criar', () => { 56 | expect(component).toBeTruthy(); 57 | }); 58 | 59 | describe('motoristas', () => { 60 | it('deve retornar motorista', () => { 61 | motoristaController.get.and.returnValue(of(new MotoristaModel())); 62 | 63 | component.ngOnInit(); 64 | 65 | expect(motoristaController.get.calls.count()).toBe(1); 66 | 67 | motoristaController.get().subscribe(motorista => { 68 | expect(motorista).toBeTruthy(); 69 | }); 70 | }); 71 | 72 | it('deve chamar o metodo adicionar motorista', () => { 73 | spyOn(dialog, 'open').and.callThrough(); 74 | 75 | component.newDriver(); 76 | 77 | expect(component.newDriver).toBeTruthy(); 78 | expect(dialog.open).toHaveBeenCalled(); 79 | }); 80 | 81 | it('deve chamar o metodo editar motorista', () => { 82 | const mock = new MotoristaModel(); 83 | 84 | component.edit(mock); 85 | 86 | expect(component.edit).toBeTruthy(); 87 | }); 88 | 89 | describe('Habilitar ou Invativar', () => { 90 | it('deve inativar um motorista', () => { 91 | const mock = new MotoristaModel(); 92 | mock.id = 1; 93 | mock.enable = false; 94 | 95 | component.disableEnable(mock); 96 | 97 | expect(motoristaController.disableEnable.calls.count()).toBe(1); 98 | 99 | motoristaController.disableEnable(mock.id, mock.enable); 100 | }); 101 | 102 | it('deve ativar um motorista', () => { 103 | const mock = new MotoristaModel(); 104 | mock.id = 1; 105 | mock.enable = true; 106 | 107 | component.disableEnable(mock); 108 | 109 | expect(motoristaController.disableEnable.calls.count()).toBe(1); 110 | 111 | motoristaController.disableEnable(mock.id, mock.enable); 112 | }); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { MatPaginator } from '@angular/material/paginator'; 3 | import { MatTableDataSource } from '@angular/material/table'; 4 | import * as _ from 'lodash'; 5 | 6 | import { finalize } from 'rxjs/operators'; 7 | import { MatDialog } from '@angular/material'; 8 | import { DialogCadastroComponent } from '../../shared/dialogs/dialog-cadastro/dialog-cadastro.component'; 9 | import { DriverEntity } from '../../../../domain/entities/driver-entity'; 10 | import { IMotoristaController } from 'src/app/domain/interfaces/controllers/imotorista-controller'; 11 | 12 | @Component({ 13 | selector: 'app-home', 14 | templateUrl: './home.component.html', 15 | styleUrls: ['./home.component.scss'] 16 | }) 17 | export class HomeComponent implements OnInit { 18 | @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; 19 | 20 | isLoading: boolean; 21 | drivers: DriverEntity[] = []; 22 | displayedColumns: string[] = ['name', 'phone', 'birth_date', 'documents', 'action']; 23 | dataSource: any; 24 | 25 | constructor( 26 | private motoristaController: IMotoristaController, 27 | private dialog: MatDialog 28 | ) { } 29 | 30 | ngOnInit() { 31 | // this.dataSource.paginator = this.paginator; 32 | 33 | this.getDrivers(); 34 | } 35 | 36 | getDrivers() { 37 | this.isLoading = true; 38 | 39 | this.motoristaController.get() 40 | .pipe(finalize(() => { 41 | this.isLoading = false; 42 | })) 43 | .subscribe((driver: DriverEntity) => { 44 | this.drivers.push(driver); 45 | this.dataSource = new MatTableDataSource(this.drivers); 46 | }); 47 | } 48 | 49 | newDriver() { 50 | this.openDialog(); 51 | } 52 | 53 | edit(param: DriverEntity) { 54 | this.openDialog(param); 55 | } 56 | 57 | disableEnable(param: DriverEntity) { 58 | param.enable = !param.enable; 59 | 60 | this.motoristaController.disableEnable(param.id, param.enable); 61 | } 62 | 63 | openDialog(driver?: DriverEntity) { 64 | const dialogRef = this.dialog.open(DialogCadastroComponent, { 65 | width: '650px', 66 | data: driver ? driver : null 67 | }); 68 | 69 | dialogRef.afterClosed().subscribe(result => this.responseDialog(result)); 70 | } 71 | 72 | responseDialog(result) { 73 | if (result) { 74 | const index = _.findIndex(this.drivers, ['id', result.id]); 75 | 76 | if (index !== -1) { 77 | this.drivers[index] = result; 78 | } else { 79 | this.drivers.push(result); 80 | } 81 | 82 | this.dataSource = new MatTableDataSource(this.drivers); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FlexLayoutModule } from '@angular/flex-layout'; 4 | 5 | import { AppMaterialModule } from 'src/app/app-material.module'; 6 | import { HomeRoutingModule } from './home-routing.module'; 7 | import { HomeComponent } from './home.component'; 8 | import { TranslateModule } from '@ngx-translate/core'; 9 | 10 | @NgModule({ 11 | declarations: [HomeComponent], 12 | imports: [ 13 | CommonModule, 14 | TranslateModule, 15 | FlexLayoutModule, 16 | AppMaterialModule, 17 | HomeRoutingModule 18 | ] 19 | }) 20 | export class HomeModule { } 21 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { LoginComponent } from './login.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: 'login', 9 | component: LoginComponent, 10 | data: { 11 | title: 'Login' 12 | } 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [RouterModule.forChild(routes)], 18 | exports: [RouterModule] 19 | }) 20 | export class LoginRoutingModule { } 21 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ 'TXT_ENTRAR' | translate }} 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | {{ 'TXT_ENTRAR' | translate }} 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex: 1; 4 | 5 | section { 6 | width: 100%; 7 | min-height: 100vh; 8 | display: flex; 9 | align-items: center; 10 | 11 | .container { 12 | max-width: 362px; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | import { NotificationService } from '../../shared/notification/notification.service'; 7 | import { SharedModule } from '../../shared/shared.module'; 8 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 9 | import { UsuarioModel } from 'src/app/core/domain/entity/usuario-model'; 10 | import { of, throwError } from 'rxjs'; 11 | import { AppMaterialModule } from 'src/app/app-material.module'; 12 | import { TranslateModule } from '@ngx-translate/core'; 13 | import { IUsuarioController } from 'src/app/core/interfaces/controllers/iusuario-controller'; 14 | 15 | describe('Component: Login', () => { 16 | let component: LoginComponent; 17 | let fixture: ComponentFixture; 18 | let usuarioController: jasmine.SpyObj; 19 | 20 | beforeEach(async(() => { 21 | const controllerSpy = jasmine.createSpyObj('IUsuarioController', ['login', 'logout']); 22 | 23 | TestBed.configureTestingModule({ 24 | declarations: [ LoginComponent ], 25 | providers: [ 26 | FormBuilder, 27 | NotificationService, 28 | { provide: IUsuarioController, useValue: controllerSpy } 29 | ], 30 | imports: [ 31 | RouterTestingModule, 32 | ReactiveFormsModule, 33 | TranslateModule.forRoot(), 34 | AppMaterialModule, 35 | BrowserAnimationsModule, 36 | SharedModule 37 | ] 38 | }) 39 | .compileComponents(); 40 | 41 | usuarioController = TestBed.get(IUsuarioController); 42 | })); 43 | 44 | beforeEach(() => { 45 | fixture = TestBed.createComponent(LoginComponent); 46 | component = fixture.componentInstance; 47 | fixture.detectChanges(); 48 | }); 49 | 50 | it('deve criar', () => { 51 | expect(component).toBeTruthy(); 52 | }); 53 | 54 | describe('login', () => { 55 | it('deve retornar um usuario', () => { 56 | const param = { 57 | username: 'test', 58 | password: '123' 59 | }; 60 | 61 | usuarioController.login.and.returnValue(of(new UsuarioModel())); 62 | 63 | component.login(); 64 | 65 | expect(usuarioController.login.calls.count()).toBe(1); 66 | 67 | usuarioController.login(param).subscribe(usuario => { 68 | if (usuario) { 69 | expect(usuario).toBeTruthy(); 70 | } 71 | }); 72 | }); 73 | 74 | it('deve retornar um usuario null', () => { 75 | const param = { 76 | username: 'test', 77 | password: '123' 78 | }; 79 | 80 | usuarioController.login.and.returnValue(of(null)); 81 | 82 | component.login(); 83 | 84 | expect(usuarioController.login.calls.count()).toBe(1); 85 | 86 | usuarioController.login(param).subscribe(usuario => { 87 | if (!usuario) { 88 | expect(usuario).toBeNull(); 89 | } 90 | }); 91 | }); 92 | 93 | it('deve retornar um erro', () => { 94 | const param = { 95 | username: '', 96 | password: '' 97 | }; 98 | 99 | usuarioController.login.and.returnValue(throwError('')); 100 | 101 | component.login(); 102 | 103 | expect(usuarioController.login.calls.count()).toBe(1); 104 | 105 | usuarioController.login(param); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 3 | import { finalize } from 'rxjs/operators'; 4 | import { MatSnackBar } from '@angular/material'; 5 | import { Router } from '@angular/router'; 6 | import { ValidationError } from 'ts.validator.fluent/dist'; 7 | 8 | import { AuthService } from 'src/app/infra/auth/auth.service'; 9 | import { NotificationService } from '../../shared/notification/notification.service'; 10 | import { IUsuarioController } from 'src/app/domain/interfaces/controllers/iusuario-controller'; 11 | import { UserEntity } from '../../../../domain/entities/user-entity'; 12 | 13 | @Component({ 14 | selector: 'app-login', 15 | templateUrl: './login.component.html', 16 | styleUrls: ['./login.component.scss'] 17 | }) 18 | export class LoginComponent implements OnInit { 19 | 20 | form: FormGroup; 21 | isLoading: boolean; 22 | 23 | constructor( 24 | private fb: FormBuilder, 25 | private authService: AuthService, 26 | private snackBar: MatSnackBar, 27 | private router: Router, 28 | private notification: NotificationService, 29 | private usuarioController: IUsuarioController 30 | ) { } 31 | 32 | ngOnInit() { 33 | this.createForm(); 34 | } 35 | 36 | private createForm() { 37 | this.form = this.fb.group({ 38 | username: ['', Validators.required], 39 | password: ['', Validators.required] 40 | }); 41 | } 42 | 43 | login() { 44 | this.isLoading = true; 45 | 46 | this.usuarioController 47 | .login(this.form.value) 48 | .pipe(finalize(() => { 49 | this.isLoading = false; 50 | })) 51 | .subscribe( 52 | (usuario: UserEntity) => this.loginResponse(usuario), 53 | (err: ValidationError[]) => this.notification.open(err) 54 | ); 55 | 56 | } 57 | 58 | loginResponse(usuario: UserEntity) { 59 | if (usuario) { 60 | this.authService.credentials = usuario; 61 | this.router.navigateByUrl('/home'); 62 | } else { 63 | this.snackBar.open('Usuário ou senha inválidos.', null, { 64 | duration: 2000 65 | }); 66 | } 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | 6 | import { LoginRoutingModule } from './login-routing.module'; 7 | import { LoginComponent } from './login.component'; 8 | import { SharedModule } from '../../shared/shared.module'; 9 | import { AppMaterialModule } from 'src/app/app-material.module'; 10 | 11 | @NgModule({ 12 | declarations: [LoginComponent], 13 | imports: [ 14 | CommonModule, 15 | ReactiveFormsModule, 16 | TranslateModule, 17 | AppMaterialModule, 18 | SharedModule, 19 | LoginRoutingModule 20 | ] 21 | }) 22 | export class LoginModule { } 23 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/pages-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { path: '**', redirectTo: '', pathMatch: 'full' } 6 | ]; 7 | 8 | @NgModule({ 9 | imports: [RouterModule.forRoot(routes, { useHash: true })], 10 | exports: [RouterModule] 11 | }) 12 | export class PagesRoutingModule { } 13 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/pages.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { PagesRoutingModule } from './pages-routing.module'; 5 | import { LoginModule } from './login/login.module'; 6 | import { HomeModule } from './home/home.module'; 7 | 8 | @NgModule({ 9 | declarations: [], 10 | imports: [ 11 | CommonModule, 12 | LoginModule, 13 | HomeModule, 14 | PagesRoutingModule 15 | ], 16 | exports: [PagesRoutingModule] 17 | }) 18 | export class PagesModule { } 19 | -------------------------------------------------------------------------------- /src/app/presentation/view/pages/route.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Routes, Route as ngRoute } from '@angular/router'; 3 | 4 | import { BaseComponent } from '../base/base.component'; 5 | import { AuthGuard } from 'src/app/infra/auth/auth.guard'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class RouteService { 11 | 12 | static withShell(routes: Routes): ngRoute { 13 | return { 14 | path: '', 15 | component: BaseComponent, 16 | children: routes, 17 | canActivate: [AuthGuard] 18 | }; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/components/input/input.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{label}} 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/components/input/input.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | 4 | .mat-form-field { 5 | width: 100%; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/components/input/input.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { InputComponent } from './input.component'; 4 | import { AppMaterialModule } from 'src/app/app-material.module'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | describe('InputComponent', () => { 8 | let component: InputComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [ InputComponent ], 14 | imports: [ 15 | AppMaterialModule, 16 | BrowserAnimationsModule 17 | ] 18 | }) 19 | .compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(InputComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('deve criar', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/components/input/input.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, forwardRef, Input, Output, EventEmitter } from '@angular/core'; 2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; 3 | 4 | @Component({ 5 | selector: 'app-input', 6 | templateUrl: './input.component.html', 7 | styleUrls: ['./input.component.scss'], 8 | providers: [ 9 | { 10 | provide: NG_VALUE_ACCESSOR, 11 | useExisting: forwardRef(() => InputComponent), 12 | multi: true 13 | } 14 | ] 15 | }) 16 | export class InputComponent implements ControlValueAccessor { 17 | 18 | @Input('t-label') _label: string = ''; 19 | @Input('t-type') _type: string = ''; 20 | @Input('t-value') _value: any = ''; 21 | @Input('t-readonly') _readonly: boolean = false; 22 | @Input('t-disabled') _disabled: boolean = false; 23 | 24 | @Output('t-change') change: EventEmitter = new EventEmitter(); 25 | 26 | onChange: any = () => { }; 27 | onTouched: any = () => { }; 28 | 29 | get label() { 30 | return this._label; 31 | } 32 | 33 | get type() { 34 | return this._type; 35 | } 36 | 37 | get value() { 38 | return this._value; 39 | } 40 | 41 | set value(val) { 42 | this._value = val; 43 | this.onChange(val); 44 | this.onTouched(); 45 | } 46 | 47 | get disabled() { 48 | return this._disabled; 49 | } 50 | 51 | set disabled(val) { 52 | this._disabled = val; 53 | } 54 | 55 | get readonly() { 56 | return this._readonly; 57 | } 58 | 59 | set readonly(val) { 60 | this._readonly = val; 61 | } 62 | 63 | changeInput(val) { 64 | this.value = val; 65 | } 66 | 67 | writeValue(obj: any): void { 68 | if (obj) { 69 | this.value = obj; 70 | } 71 | } 72 | registerOnChange(fn: any): void { 73 | this.onChange = fn; 74 | } 75 | registerOnTouched(fn: any): void { 76 | this.onTouched = fn; 77 | } 78 | setDisabledState?(isDisabled: boolean): void { 79 | this.disabled = isDisabled; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/dialogs/dialog-cadastro/dialog-cadastro.component.html: -------------------------------------------------------------------------------- 1 | {{data ? 'Editar Motorista #' + data.id : 'TXT_CADASTRAR_MOTORISTA' | translate}} 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | {{ 'TXT_TIPO_DOCUMENTO' | translate }}: 28 | 29 | {{ 'TXT_CPF' | translate }} 30 | {{ 'TXT_CNH' | translate }} 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {{ 'TXT_CANCELAR' | translate }} 56 | {{ 'TXT_SALVAR' | translate }} 57 | 58 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/dialogs/dialog-cadastro/dialog-cadastro.component.scss: -------------------------------------------------------------------------------- 1 | .mat-dialog-actions { 2 | padding: 24px 0; 3 | } 4 | 5 | .t-input-category { 6 | ::ng-deep { 7 | .mat-input-element { 8 | text-transform: uppercase; 9 | } 10 | } 11 | } 12 | 13 | .t-radio-group { 14 | display: flex; 15 | margin: 15px 0; 16 | 17 | .t-radio-button { 18 | margin-right: 15px; 19 | } 20 | } 21 | 22 | .t-btn-add { 23 | margin: 6px 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/dialogs/dialog-cadastro/dialog-cadastro.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DialogCadastroComponent } from './dialog-cadastro.component'; 4 | import { MotoristaModel } from 'src/app/core/domain/entity/motorista-model'; 5 | import { IMotoristaController } from 'src/app/core/interfaces/controllers/imotorista-controller'; 6 | import { FormBuilder } from '@angular/forms'; 7 | import { MatDialogRef } from '@angular/material'; 8 | import { NotificationService } from '../../notification/notification.service'; 9 | import { TranslateModule } from '@ngx-translate/core'; 10 | 11 | describe('DialogCadastroComponent:', () => { 12 | let component: DialogCadastroComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [ DialogCadastroComponent ], 18 | providers: [ 19 | MotoristaModel, 20 | IMotoristaController, 21 | FormBuilder, 22 | MatDialogRef, 23 | NotificationService 24 | ], 25 | imports: [ 26 | TranslateModule.forRoot() 27 | ] 28 | }) 29 | .compileComponents(); 30 | })); 31 | 32 | beforeEach(() => { 33 | fixture = TestBed.createComponent(DialogCadastroComponent); 34 | component = fixture.componentInstance; 35 | fixture.detectChanges(); 36 | }); 37 | 38 | xit('deve criar', () => { 39 | expect(component).toBeTruthy(); 40 | }); 41 | 42 | xit('deve criar o form', () => { 43 | component.createForm(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/dialogs/dialog-cadastro/dialog-cadastro.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject } from '@angular/core'; 2 | import { FormGroup, FormBuilder, Validators, FormArray, FormControl } from '@angular/forms'; 3 | import { finalize } from 'rxjs/operators'; 4 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; 5 | import * as _ from 'lodash'; 6 | 7 | import { NotificationService } from '../../notification/notification.service'; 8 | import { DriverEntity } from '../../../../../domain/entities/driver-entity'; 9 | import { IMotoristaController } from 'src/app/domain/interfaces/controllers/imotorista-controller'; 10 | import { DocumentsEntity } from '../../../../../domain/entities/documents-entity'; 11 | 12 | @Component({ 13 | selector: 'app-dialog-cadastro', 14 | templateUrl: './dialog-cadastro.component.html', 15 | styleUrls: ['./dialog-cadastro.component.scss'] 16 | }) 17 | export class DialogCadastroComponent implements OnInit { 18 | 19 | form: FormGroup; 20 | documents: FormArray; 21 | isLoading: boolean; 22 | 23 | constructor( 24 | @Inject(MAT_DIALOG_DATA) public data: DriverEntity 25 | , 26 | private motoristaController: IMotoristaController, 27 | private fb: FormBuilder, 28 | private dialogRef: MatDialogRef, 29 | private notification: NotificationService 30 | ) { } 31 | 32 | ngOnInit() { 33 | this.createForm(); 34 | } 35 | 36 | createForm() { 37 | this.form = this.fb.group({ 38 | id: [this.data ? this.data.id : ''], 39 | name: [this.data ? this.data.name : '', Validators.required], 40 | birth_date: [this.data ? this.data.birth_date : '', Validators.required], 41 | phone: [this.data ? this.data.phone : '', Validators.required], 42 | documents: this.data ? this.fb.array([]) : this.fb.array([this.createDocument()]) 43 | }); 44 | 45 | if (this.data) { 46 | this.addDocuments(this.data.documents); 47 | } 48 | } 49 | 50 | createDocument(document?: DocumentsEntity): FormGroup { 51 | return this.fb.group({ 52 | number: [document ? document.number : '', Validators.required], 53 | category: document ? document.category : '', 54 | doc_type: [document ? document.doc_type : '', Validators.required], 55 | add_document: document ? true : false 56 | }); 57 | } 58 | 59 | addDocuments(documents: DocumentsEntity[]) { 60 | documents.forEach((item) => { 61 | this.documents = this.form.get('documents') as FormArray; 62 | this.documents.push(this.createDocument(item)); 63 | }); 64 | } 65 | 66 | addNewDocument(item: FormControl): void { 67 | item.get('add_document').setValue(true); 68 | 69 | this.documents = this.form.get('documents') as FormArray; 70 | this.documents.push(this.createDocument()); 71 | } 72 | 73 | removeDocument(item: FormControl, index: number): void { 74 | this.documents.controls.splice(index, 1); 75 | this.documents.value.splice(index, 1); 76 | 77 | if (this.documents.value.length === 0) { 78 | this.documents = this.form.get('documents') as FormArray; 79 | this.documents.push(this.createDocument()); 80 | } 81 | } 82 | 83 | save() { 84 | this.isLoading = true; 85 | 86 | this.data ? this.update() : this.insert(); 87 | } 88 | 89 | update() { 90 | this.motoristaController.update(this.form.value) 91 | .pipe(finalize(() => { 92 | this.isLoading = false; 93 | })) 94 | .subscribe((driver: DriverEntity) => this.dialogRef.close(driver), err => this.notification.open(err)); 95 | } 96 | 97 | insert() { 98 | this.motoristaController.insert(this.form.value) 99 | .pipe(finalize(() => { 100 | this.isLoading = false; 101 | })) 102 | .subscribe((driver: DriverEntity) => this.dialogRef.close(driver), err => this.notification.open(err)); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/app/presentation/view/shared/notification/notification.component.scss -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NotificationComponent } from './notification.component'; 4 | import { MatSnackBar } from '@angular/material'; 5 | 6 | describe('NotificationComponent', () => { 7 | let component: NotificationComponent; 8 | let fixture: ComponentFixture; 9 | let matSnackBar: MatSnackBar; 10 | 11 | beforeEach(async(() => { 12 | const spy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); 13 | 14 | TestBed.configureTestingModule({ 15 | declarations: [ NotificationComponent ], 16 | providers: [ 17 | { provide: MatSnackBar, useValue: spy } 18 | ] 19 | }) 20 | .compileComponents(); 21 | 22 | matSnackBar = TestBed.get(MatSnackBar); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(NotificationComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | xit('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-notification', 5 | templateUrl: './notification.component.html', 6 | styleUrls: ['./notification.component.scss'] 7 | }) 8 | export class NotificationComponent implements OnInit { 9 | 10 | message: string; 11 | 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { NotificationService } from './notification.service'; 4 | import { MatSnackBar } from '@angular/material'; 5 | 6 | describe('NotificationService:', () => { 7 | let service: NotificationService; 8 | let valueSpy: jasmine.SpyObj; 9 | 10 | beforeEach(() => { 11 | const spy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent', 'onAction']); 12 | 13 | TestBed.configureTestingModule({ 14 | providers: [ 15 | { provide: MatSnackBar, useValue: spy } 16 | ] 17 | }); 18 | 19 | service = TestBed.get(NotificationService); 20 | valueSpy = TestBed.get(MatSnackBar); 21 | }); 22 | 23 | xit('deve ser criado', () => { 24 | expect(service).toBeTruthy(); 25 | }); 26 | 27 | xit('deve executar o metodo open()', () => { 28 | const err = true; 29 | 30 | service.open(err); 31 | 32 | expect(valueSpy.dismiss.calls.count()).toBe(1); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/notification/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ValidationResult, ValidationError } from 'ts.validator.fluent/dist'; 3 | import { MatSnackBar, MatSnackBarRef } from '@angular/material'; 4 | import * as _ from 'lodash'; 5 | 6 | import { NotificationComponent } from './notification.component'; 7 | import { Observable } from 'rxjs'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class NotificationService { 13 | 14 | snackBarRef: MatSnackBarRef; 15 | 16 | constructor( 17 | private snackBar: MatSnackBar 18 | ) { } 19 | 20 | open(err: any): Observable { 21 | const errs: string[] = []; 22 | 23 | _.forEach(err, (x: ValidationError) => { 24 | const msg = x.Message; 25 | 26 | errs.push(msg + ''); 27 | }); 28 | 29 | this.snackBarRef = this.snackBar.openFromComponent(NotificationComponent, { 30 | duration: 2000 31 | }); 32 | 33 | this.snackBarRef.instance.message = _.join(errs, ' '); 34 | 35 | return this.snackBarRef.onAction(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/presentation/view/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | 6 | import { InputComponent } from './components/input/input.component'; 7 | import { DialogCadastroComponent } from './dialogs/dialog-cadastro/dialog-cadastro.component'; 8 | import { NotificationComponent } from './notification/notification.component'; 9 | import { AppMaterialModule } from 'src/app/app-material.module'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | InputComponent, 14 | DialogCadastroComponent, 15 | NotificationComponent 16 | ], 17 | exports: [InputComponent], 18 | imports: [ 19 | CommonModule, 20 | ReactiveFormsModule, 21 | TranslateModule, 22 | AppMaterialModule 23 | ], 24 | entryComponents: [ 25 | DialogCadastroComponent, 26 | NotificationComponent 27 | ] 28 | }) 29 | export class SharedModule { } 30 | -------------------------------------------------------------------------------- /src/app/presentation/view/view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { BaseModule } from './base/base.module'; 5 | import { PagesModule } from './pages/pages.module'; 6 | import { SharedModule } from './shared/shared.module'; 7 | 8 | @NgModule({ 9 | declarations: [], 10 | imports: [ 11 | CommonModule, 12 | BaseModule, 13 | PagesModule, 14 | SharedModule 15 | ], 16 | exports: [ 17 | PagesModule 18 | ] 19 | }) 20 | export class ViewModule { } 21 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/i18n/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "TXT_ENTRAR": "Entrar", 3 | "TXT_USUARIO": "Usuário", 4 | "TXT_SENHA": "Senha", 5 | "TXT_MOTORISTAS": "Motoristas", 6 | "TXT_NOME": "Nome", 7 | "TXT_TELEFONE": "Telefone", 8 | "TXT_DATA_NASCIMENTO": "Data de Nasc", 9 | "TXT_DOCUMENTO": "Documento", 10 | "TXT_ACOES": "Ações", 11 | "TXT_EDITAR": "Editar", 12 | "TXT_ATIVAR": "Ativar", 13 | "TXT_INATIVAR": "Inativar", 14 | "TXT_NOVO_MOTORISTA": "Novo Motorista", 15 | "TXT_CADASTRAR_MOTORISTA": "Cadastrar Motorista", 16 | "TXT_TIPO_DOCUMENTO": "Tipo de documento", 17 | "TXT_CPF": "CPF", 18 | "TXT_CNH": "CNH", 19 | "TXT_NUMERO": "Número", 20 | "TXT_CATEGORIA": "Categoria", 21 | "TXT_CANCELAR": "Cancelar", 22 | "TXT_SALVAR": "Salvar", 23 | "TXT_OLA": "Olá", 24 | "TXT_SAIR": "SAIR", 25 | "validateRequired": "O campo {{0}} é obrigatório.", 26 | "validateMaximumSize": "O campo {{0}} deve possuir até {{1}} caracteres." 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/images/img-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/assets/images/img-avatar.jpg -------------------------------------------------------------------------------- /src/backend/server.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | const data = { 3 | usuarios: [], 4 | motoristas: [] 5 | } 6 | 7 | data.usuarios.push({ 8 | id: 1, 9 | username: 'test', 10 | password: '123', 11 | email: 'contato@test.com.br', 12 | token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ1c3VhcmlvMSIsInNlbmhhIjoiMTIzIn0.Pu_q8I5wAFYKMoRHx89SALV2zRE9YvfmF6WYthpDLbU' 13 | }); 14 | 15 | data.motoristas = [ 16 | { 17 | "id": 1, 18 | "name": "Pouca Tripa", 19 | "birth_date": "1976-09-22T00:00:00", 20 | "state": "São Paulo", 21 | "city": "São Paulo", 22 | "enable": true, 23 | "addresses": { 24 | "name": "Casa", 25 | "state": "São Paulo", 26 | "country": "BR", 27 | "neighborhood": "CENTRO", 28 | "city": "São Paulo", 29 | "street_number": 24, 30 | "complement": "apartamento", 31 | "postal_code": "01300-000", 32 | "street_name": "Avenida Paulista" 33 | }, 34 | "documents": [ 35 | { 36 | "expires_at": "2010-11-23T00:00:00+00:00", 37 | "country": "BR", 38 | "number": "700441702", 39 | "doc_type": "CNH", 40 | "category": "AB" 41 | } 42 | ] 43 | }, 44 | { 45 | "id": 2, 46 | "name": "Quase nada", 47 | "birth_date": "1986-09-22T00:00:00", 48 | "state": "Rio de Janeiro", 49 | "city": "Niterói", 50 | "enable": true, 51 | "addresses": { 52 | "name": "", 53 | "state": "", 54 | "country": "", 55 | "neighborhood": "", 56 | "city": "", 57 | "street_number": "", 58 | "complement": "", 59 | "postal_code": "", 60 | "street_name": "" 61 | }, 62 | "documents": [ 63 | { 64 | "country": "BR", 65 | "number": "12312312377", 66 | "doc_type": "CPF" 67 | } 68 | ] 69 | } 70 | ]; 71 | 72 | return data; 73 | } 74 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | version: '1.0.0', 4 | serverUrl: 'http://localhost:3000', 5 | defaultLanguage: 'pt-BR', 6 | supportedLanguages: [ 7 | 'pt-BR' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | version: '1.0.0', 8 | serverUrl: 'http://localhost:3000', 9 | defaultLanguage: 'pt-BR', 10 | supportedLanguages: [ 11 | 'pt-BR' 12 | ] 13 | }; 14 | 15 | /* 16 | * For easier debugging in development mode, you can import the following file 17 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 18 | * 19 | * This import should be commented out in production mode because it will have a negative impact 20 | * on performance if an error is thrown. 21 | */ 22 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 23 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leandro-mancini/angular-clean-architecture/d4b2eac41ef874239eaf624292e5a6d7a9b09f0a/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Clean Architecture 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | Please enable JavaScript to continue using this application. 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /src/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-clean-architecture", 3 | "short_name": "angular-clean-architecture", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/stories/index.stories.ts: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/angular'; 2 | import { action } from '@storybook/addon-actions'; 3 | import { linkTo } from '@storybook/addon-links'; 4 | 5 | import { Welcome, Button } from '@storybook/angular/demo'; 6 | 7 | storiesOf('Welcome', module).add('to Storybook', () => ({ 8 | component: Welcome, 9 | props: {}, 10 | })); 11 | 12 | storiesOf('Button', module) 13 | .add('with text', () => ({ 14 | component: Button, 15 | props: { 16 | text: 'Hello Button', 17 | }, 18 | })) 19 | .add( 20 | 'with some emoji', 21 | () => ({ 22 | component: Button, 23 | props: { 24 | text: '😀 😎 👍 💯', 25 | }, 26 | }), 27 | { notes: 'My notes on a button with emojis' } 28 | ) 29 | .add( 30 | 'with some emoji and action', 31 | () => ({ 32 | component: Button, 33 | props: { 34 | text: '😀 😎 👍 💯', 35 | onClick: action('This was clicked OMG'), 36 | }, 37 | }), 38 | { notes: 'My notes on a button with emojis' } 39 | ); 40 | 41 | storiesOf('Another Button', module).add('button with link to another story', () => ({ 42 | component: Button, 43 | props: { 44 | text: 'Go to Welcome Story', 45 | onClick: linkTo('Welcome'), 46 | }, 47 | })); 48 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'node_modules/bootstrap/dist/css/bootstrap'; 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'automapper-ts'; 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts", 13 | "**/*.stories.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "importHelpers": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warn" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } --------------------------------------------------------------------------------
30 | {{item.number}} 31 | / {{item.category}} 32 |
72 | {{doc.number}} 73 | / {{doc.category}} 74 |