feature/upgrade #12
					 96 changed files with 13140 additions and 11184 deletions
				
			
		
							
								
								
									
										12
									
								
								.drone.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.drone.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| kind: pipeline | ||||
| type: docker | ||||
| name: default | ||||
| 
 | ||||
| steps: | ||||
|   - name: install | ||||
|     image: node:20-alpine | ||||
|     commands: | ||||
|       - apk add firefox | ||||
|       - npm install | ||||
|       - npm run test:ci | ||||
|       - npm run build | ||||
|  | @ -1,4 +1,4 @@ | |||
| # Editor configuration, see http://editorconfig.org | ||||
| # Editor configuration, see https://editorconfig.org | ||||
| root = true | ||||
| 
 | ||||
| [*] | ||||
|  | @ -8,6 +8,9 @@ indent_size = 2 | |||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
| 
 | ||||
| [*.ts] | ||||
| quote_type = single | ||||
| 
 | ||||
| [*.md] | ||||
| max_line_length = off | ||||
| trim_trailing_whitespace = false | ||||
|  |  | |||
							
								
								
									
										28
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1,44 +1,42 @@ | |||
| # See http://help.github.com/ignore-files/ for more about ignoring files. | ||||
| 
 | ||||
| # compiled output | ||||
| # Compiled output | ||||
| /dist | ||||
| /tmp | ||||
| /out-tsc | ||||
| /bazel-out | ||||
| 
 | ||||
| # dependencies | ||||
| # Node | ||||
| /node_modules | ||||
| npm-debug.log | ||||
| yarn-error.log | ||||
| 
 | ||||
| # IDEs and editors | ||||
| /.idea | ||||
| .idea/ | ||||
| .project | ||||
| .classpath | ||||
| .c9/ | ||||
| *.launch | ||||
| .settings/ | ||||
| *.sublime-workspace | ||||
| *.iml | ||||
| 
 | ||||
| # IDE - VSCode | ||||
| .vscode/* | ||||
| # Visual Studio Code | ||||
| .vscode/tasks.json | ||||
| !.vscode/settings.json | ||||
| !.vscode/tasks.json | ||||
| !.vscode/launch.json | ||||
| !.vscode/extensions.json | ||||
| .history/* | ||||
| 
 | ||||
| # misc | ||||
| /.sass-cache | ||||
| # Miscellaneous | ||||
| /.angular/cache | ||||
| .sass-cache/ | ||||
| /connect.lock | ||||
| /coverage | ||||
| /libpeerconnection.log | ||||
| npm-debug.log | ||||
| testem.log | ||||
| /typings | ||||
| 
 | ||||
| # e2e | ||||
| /e2e/*.js | ||||
| /e2e/*.map | ||||
| 
 | ||||
| # System Files | ||||
| # System files | ||||
| .DS_Store | ||||
| Thumbs.db | ||||
| *~ | ||||
|  |  | |||
							
								
								
									
										14
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,8 +1,8 @@ | |||
| # convertorizr - Convert whatever you want! | ||||
| # Convertorizr - Convert whatever you want! | ||||
| 
 | ||||
| This is a short introduction to the awesome Convertorizr hosted at https://conv.friedli.info/. | ||||
| 
 | ||||
| Deployment is automated with Gitlab CI. Usage is self-explanatory. What else do you need to know? | ||||
| Continuous integration is automated with Drone CI ([](https://ci.gittr.ch/manuel/converter)). Usage is self-explanatory. What else do you need to know? | ||||
| 
 | ||||
| The source code is hosted at https://gittr.ch/manuel/converter.git. | ||||
| Contact the author at manuel-convertorizr|at|fritteli.ch. | ||||
|  | @ -11,11 +11,11 @@ Cheers! | |||
| 
 | ||||
| # Technical stuff | ||||
| 
 | ||||
| This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.1.5. | ||||
| This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.0. | ||||
| 
 | ||||
| ## Development server | ||||
| 
 | ||||
| Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. | ||||
| Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. | ||||
| 
 | ||||
| ## Code scaffolding | ||||
| 
 | ||||
|  | @ -23,7 +23,7 @@ Run `ng generate component component-name` to generate a new component. You can | |||
| 
 | ||||
| ## Build | ||||
| 
 | ||||
| Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. | ||||
| Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. | ||||
| 
 | ||||
| ## Running unit tests | ||||
| 
 | ||||
|  | @ -31,8 +31,8 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github. | |||
| 
 | ||||
| ## Running end-to-end tests | ||||
| 
 | ||||
| Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). | ||||
| Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. | ||||
| 
 | ||||
| ## Further help | ||||
| 
 | ||||
| To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). | ||||
| To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. | ||||
|  |  | |||
							
								
								
									
										127
									
								
								angular.json
									
										
									
									
									
								
							
							
						
						
									
										127
									
								
								angular.json
									
										
									
									
									
								
							|  | @ -3,21 +3,28 @@ | |||
|   "version": 1, | ||||
|   "newProjectRoot": "projects", | ||||
|   "projects": { | ||||
|     "converter": { | ||||
|     "convertorizr": { | ||||
|       "projectType": "application", | ||||
|       "schematics": { | ||||
|         "@schematics/angular:component": { | ||||
|           "style": "scss" | ||||
|         } | ||||
|       }, | ||||
|       "root": "", | ||||
|       "sourceRoot": "src", | ||||
|       "projectType": "application", | ||||
|       "prefix": "app", | ||||
|       "schematics": {}, | ||||
|       "architect": { | ||||
|         "build": { | ||||
|           "builder": "@angular-devkit/build-angular:browser", | ||||
|           "builder": "@angular-devkit/build-angular:application", | ||||
|           "options": { | ||||
|             "outputPath": "dist/converter", | ||||
|             "outputPath": "dist/convertorizr", | ||||
|             "index": "src/index.html", | ||||
|             "main": "src/main.ts", | ||||
|             "polyfills": "src/polyfills.ts", | ||||
|             "tsConfig": "src/tsconfig.app.json", | ||||
|             "browser": "src/main.ts", | ||||
|             "polyfills": [ | ||||
|               "zone.js" | ||||
|             ], | ||||
|             "tsConfig": "tsconfig.app.json", | ||||
|             "inlineStyleLanguage": "scss", | ||||
|             "assets": [ | ||||
|               "src/favicon.ico", | ||||
|               "src/assets" | ||||
|  | @ -29,99 +36,67 @@ | |||
|           }, | ||||
|           "configurations": { | ||||
|             "production": { | ||||
|               "fileReplacements": [ | ||||
|               "budgets": [ | ||||
|                 { | ||||
|                   "replace": "src/environments/environment.ts", | ||||
|                   "with": "src/environments/environment.prod.ts" | ||||
|                   "type": "initial", | ||||
|                   "maximumWarning": "500kb", | ||||
|                   "maximumError": "1mb" | ||||
|                 }, | ||||
|                 { | ||||
|                   "type": "anyComponentStyle", | ||||
|                   "maximumWarning": "2kb", | ||||
|                   "maximumError": "4kb" | ||||
|                 } | ||||
|               ], | ||||
|               "optimization": true, | ||||
|               "outputHashing": "all", | ||||
|               "sourceMap": false, | ||||
|               "extractCss": true, | ||||
|               "namedChunks": false, | ||||
|               "aot": true, | ||||
|               "extractLicenses": true, | ||||
|               "vendorChunk": false, | ||||
|               "buildOptimizer": true | ||||
|             } | ||||
|               "outputHashing": "all" | ||||
|             }, | ||||
|             "development": { | ||||
|               "optimization": false, | ||||
|               "extractLicenses": false, | ||||
|               "sourceMap": true | ||||
|             } | ||||
|           }, | ||||
|           "defaultConfiguration": "production" | ||||
|         }, | ||||
|         "serve": { | ||||
|           "builder": "@angular-devkit/build-angular:dev-server", | ||||
|           "options": { | ||||
|             "browserTarget": "converter:build" | ||||
|           }, | ||||
|           "configurations": { | ||||
|             "production": { | ||||
|               "browserTarget": "converter:build:production" | ||||
|             } | ||||
|               "buildTarget": "convertorizr:build:production" | ||||
|             }, | ||||
|             "development": { | ||||
|               "buildTarget": "convertorizr:build:development" | ||||
|             } | ||||
|           }, | ||||
|           "defaultConfiguration": "development" | ||||
|         }, | ||||
|         "extract-i18n": { | ||||
|           "builder": "@angular-devkit/build-angular:extract-i18n", | ||||
|           "options": { | ||||
|             "browserTarget": "converter:build" | ||||
|             "buildTarget": "convertorizr:build" | ||||
|           } | ||||
|         }, | ||||
|         "test": { | ||||
|           "builder": "@angular-devkit/build-angular:karma", | ||||
|           "options": { | ||||
|             "main": "src/test.ts", | ||||
|             "polyfills": "src/polyfills.ts", | ||||
|             "tsConfig": "src/tsconfig.spec.json", | ||||
|             "karmaConfig": "src/karma.conf.js", | ||||
|             "polyfills": [ | ||||
|               "zone.js", | ||||
|               "zone.js/testing" | ||||
|             ], | ||||
|             "tsConfig": "tsconfig.spec.json", | ||||
|             "inlineStyleLanguage": "scss", | ||||
|             "assets": [ | ||||
|               "src/favicon.ico", | ||||
|               "src/assets" | ||||
|             ], | ||||
|             "styles": [ | ||||
|               "src/styles.scss" | ||||
|             ], | ||||
|             "scripts": [], | ||||
|             "assets": [ | ||||
|               "src/favicon.ico", | ||||
|               "src/assets" | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         "lint": { | ||||
|           "builder": "@angular-devkit/build-angular:tslint", | ||||
|           "options": { | ||||
|             "tsConfig": [ | ||||
|               "src/tsconfig.app.json", | ||||
|               "src/tsconfig.spec.json" | ||||
|             ], | ||||
|             "exclude": [ | ||||
|               "**/node_modules/**" | ||||
|             ] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "converter-e2e": { | ||||
|       "root": "e2e/", | ||||
|       "projectType": "application", | ||||
|       "architect": { | ||||
|         "e2e": { | ||||
|           "builder": "@angular-devkit/build-angular:protractor", | ||||
|           "options": { | ||||
|             "protractorConfig": "e2e/protractor.conf.js", | ||||
|             "devServerTarget": "converter:serve" | ||||
|           }, | ||||
|           "configurations": { | ||||
|             "production": { | ||||
|               "devServerTarget": "converter:serve:production" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "lint": { | ||||
|           "builder": "@angular-devkit/build-angular:tslint", | ||||
|           "options": { | ||||
|             "tsConfig": "e2e/tsconfig.e2e.json", | ||||
|             "exclude": [ | ||||
|               "**/node_modules/**" | ||||
|             ] | ||||
|             "karmaConfig": "karma.conf.js" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   }, | ||||
|   "defaultProject": "converter" | ||||
| } | ||||
|  |  | |||
|  | @ -1,31 +0,0 @@ | |||
| import {ConvertorizrPage} from './app.po'; | ||||
| 
 | ||||
| describe('convertorizr App', () => { | ||||
|   let page: ConvertorizrPage; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     page = new ConvertorizrPage(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should display a textarea that is initially empty', () => { | ||||
|     page.navigateTo() | ||||
|       .then(() => page.getInputFieldContent(0)) | ||||
|       .then((value: string) => { | ||||
|         expect(value).toEqual(''); | ||||
|       }); | ||||
|   }); | ||||
| 
 | ||||
|   it('should convert a string to its base64 representation', () => { | ||||
|     page.navigateTo() | ||||
|       .then(() => page.setInputFieldContent(0, 'Hello, World!')) | ||||
|       .then(() => page.getSelectedConverterOption(0)) | ||||
|       .then((option: string) => { | ||||
|         expect(option).toEqual('Select conversion ...'); | ||||
|       }) | ||||
|       .then(() => page.selectConverterOption(0, 'Encode Base 64')) | ||||
|       .then(() => page.getInputFieldContent(1)) | ||||
|       .then((content: string) => { | ||||
|         expect(content).toEqual('SGVsbG8sIFdvcmxkIQ=='); | ||||
|       }); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,44 +0,0 @@ | |||
| import {browser, by, element, ElementFinder} from 'protractor'; | ||||
| import {promise, WebElementPromise} from 'selenium-webdriver'; | ||||
| 
 | ||||
| export class ConvertorizrPage { | ||||
|   navigateTo(): promise.Promise<any> { | ||||
|     return browser.get('/'); | ||||
|   } | ||||
| 
 | ||||
|   private getInputField(index: number): WebElementPromise { | ||||
|     return element | ||||
|       .all(by.css('app-root div.inputwrapper')) | ||||
|       .get(index) | ||||
|       .element(by.css('.textwrapper textarea')) | ||||
|       .getWebElement(); | ||||
|   } | ||||
| 
 | ||||
|   getInputFieldContent(index: number): promise.Promise<string> { | ||||
|     return this.getInputField(index).getText(); | ||||
|   } | ||||
| 
 | ||||
|   setInputFieldContent(index: number, content: string): promise.Promise<void> { | ||||
|     return this.getInputField(index).sendKeys(content); | ||||
|   } | ||||
| 
 | ||||
|   private getConverterDropdown(index: number): ElementFinder { | ||||
|     return element | ||||
|       .all(by.css('app-root div.inputwrapper')) | ||||
|       .get(index) | ||||
|       .element(by.css('.selectwrapper select')); | ||||
|   } | ||||
| 
 | ||||
|   getSelectedConverterOption(index: number): promise.Promise<string> { | ||||
|     return this.getConverterDropdown(index) | ||||
|       .$('option:checked') | ||||
|       .getWebElement() | ||||
|       .getText(); | ||||
|   } | ||||
| 
 | ||||
|   selectConverterOption(index: number, optionName: string): promise.Promise<void> { | ||||
|     return this.getConverterDropdown(index) | ||||
|       .element(by.cssContainingText('option', optionName)) | ||||
|       .click(); | ||||
|   } | ||||
| } | ||||
|  | @ -1,13 +0,0 @@ | |||
| { | ||||
|   "extends": "../tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "../out-tsc/app", | ||||
|     "module": "commonjs", | ||||
|     "target": "es5", | ||||
|     "types": [ | ||||
|       "jasmine", | ||||
|       "jasminewd2", | ||||
|       "node" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
							
								
								
									
										40
									
								
								karma.conf.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								karma.conf.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| // Karma configuration file, see link for more information
 | ||||
| // https://karma-runner.github.io/1.0/config/configuration-file.html
 | ||||
| 
 | ||||
| module.exports = function (config) { | ||||
|   config.set({ | ||||
|     basePath: '', | ||||
|     frameworks: ['jasmine', '@angular-devkit/build-angular'], | ||||
|     plugins: [ | ||||
|       require('karma-jasmine'), | ||||
|       require('karma-chrome-launcher'), | ||||
|       require('karma-firefox-launcher'), | ||||
|       require('karma-jasmine-html-reporter'), | ||||
|       require('karma-coverage'), | ||||
|       require('@angular-devkit/build-angular/plugins/karma') | ||||
|     ], | ||||
|     client: { | ||||
|       jasmine: { | ||||
|         // you can add configuration options for Jasmine here
 | ||||
|         // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
 | ||||
|         // for example, you can disable the random execution with `random: false`
 | ||||
|         // or set a specific seed with `seed: 4321`
 | ||||
|       }, | ||||
|       clearContext: false // leave Jasmine Spec Runner output visible in browser
 | ||||
|     }, | ||||
|     jasmineHtmlReporter: { | ||||
|       suppressAll: true // removes the duplicated traces
 | ||||
|     }, | ||||
|     coverageReporter: { | ||||
|       dir: require('path').join(__dirname, './coverage/convertorizr'), | ||||
|       subdir: '.', | ||||
|       reporters: [ | ||||
|         { type: 'html' }, | ||||
|         { type: 'text-summary' } | ||||
|       ] | ||||
|     }, | ||||
|     reporters: ['progress', 'kjhtml'], | ||||
|     browsers: ['Firefox'], | ||||
|     restartOnFileChange: true | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										22147
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										22147
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										75
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										75
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "convertorizr", | ||||
|   "version": "1.3.0", | ||||
|   "version": "2.0.0-dev.1", | ||||
|   "description": "Decode or encode base64, punycode, HTML entities, URI components, ...", | ||||
|   "keywords": [ | ||||
|     "encode", | ||||
|  | @ -14,7 +14,7 @@ | |||
|     "email": "manuel@fritteli.ch" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "homepage": "https://manuel.pages.gittr.ch/dencode.org", | ||||
|   "homepage": "https://conv.friedli.info/", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "https://gittr.ch/manuel/converter.git" | ||||
|  | @ -22,47 +22,44 @@ | |||
|   "scripts": { | ||||
|     "ng": "ng", | ||||
|     "start": "ng serve", | ||||
|     "build": "ng build --delete-output-path", | ||||
|     "build-prod": "ng build --prod --optimization --aot --delete-output-path --build-optimizer", | ||||
|     "test": "ng test", | ||||
|     "lint": "ng lint", | ||||
|     "e2e": "ng e2e", | ||||
|     "postinstall": "npm rebuild node-sass" | ||||
|     "build": "ng build", | ||||
|     "watch": "ng build --watch --configuration development", | ||||
|     "test": "ng test --browsers=Chromium,Firefox", | ||||
|     "test:ci": "ng test --no-watch --no-progress --browsers=FirefoxHeadless" | ||||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@angular/common": "6.1.6", | ||||
|     "@angular/compiler": "6.1.6", | ||||
|     "@angular/core": "6.1.6", | ||||
|     "@angular/forms": "6.1.6", | ||||
|     "@angular/platform-browser": "6.1.6", | ||||
|     "@angular/platform-browser-dynamic": "6.1.6", | ||||
|     "@angular/router": "6.1.6", | ||||
|     "core-js": "^2.5.1", | ||||
|     "punycode": "^2.1.0", | ||||
|     "quoted-printable": "^1.0.0", | ||||
|     "rxjs": "^6.3.0", | ||||
|     "utf8": "^2.1.0", | ||||
|     "zone.js": "^0.8.26" | ||||
|     "@angular/animations": "^17.1.0", | ||||
|     "@angular/common": "^17.1.0", | ||||
|     "@angular/compiler": "^17.1.0", | ||||
|     "@angular/core": "^17.1.0", | ||||
|     "@angular/forms": "^17.1.0", | ||||
|     "@angular/platform-browser": "^17.1.0", | ||||
|     "@angular/platform-browser-dynamic": "^17.1.0", | ||||
|     "@angular/router": "^17.1.0", | ||||
|     "punycode": "^2.3.1", | ||||
|     "quoted-printable": "^1.0.1", | ||||
|     "rxjs": "~7.8.0", | ||||
|     "tslib": "^2.3.0", | ||||
|     "utf8": "^3.0.0", | ||||
|     "zone.js": "~0.14.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@angular-devkit/build-angular": "~0.7.0", | ||||
|     "@angular/cli": "6.1.5", | ||||
|     "@angular/compiler-cli": "6.1.6", | ||||
|     "@types/jasmine": "~2.8.6", | ||||
|     "@types/jasminewd2": "~2.0.3", | ||||
|     "@types/node": "~8.9.4", | ||||
|     "codelyzer": "~4.2.1", | ||||
|     "jasmine-core": "~2.99.1", | ||||
|     "jasmine-spec-reporter": "~4.2.1", | ||||
|     "karma": "~1.7.1", | ||||
|     "karma-chrome-launcher": "~2.2.0", | ||||
|     "karma-coverage-istanbul-reporter": "~2.0.0", | ||||
|     "karma-jasmine": "~1.1.1", | ||||
|     "karma-jasmine-html-reporter": "^0.2.2", | ||||
|     "protractor": "~5.4.0", | ||||
|     "ts-node": "~5.0.1", | ||||
|     "tslint": "~5.9.1", | ||||
|     "typescript": "2.9.2" | ||||
|     "@angular-devkit/build-angular": "^17.1.0", | ||||
|     "@angular/cli": "^17.1.0", | ||||
|     "@angular/compiler-cli": "^17.1.0", | ||||
|     "@types/jasmine": "~5.1.0", | ||||
|     "@types/node": "^20.11.5", | ||||
|     "@types/punycode": "^2.1.3", | ||||
|     "@types/quoted-printable": "^1.0.2", | ||||
|     "@types/utf8": "^3.0.3", | ||||
|     "jasmine-core": "~5.1.0", | ||||
|     "karma": "~6.4.0", | ||||
|     "karma-chrome-launcher": "~3.2.0", | ||||
|     "karma-coverage": "~2.2.0", | ||||
|     "karma-firefox-launcher": "^2.1.2", | ||||
|     "karma-jasmine": "~5.1.0", | ||||
|     "karma-jasmine-html-reporter": "~2.1.0", | ||||
|     "typescript": "~5.3.2" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,16 +0,0 @@ | |||
| import {NgModule} from '@angular/core'; | ||||
| import {RouterModule, Routes} from '@angular/router'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|   { | ||||
|     path: '', | ||||
|     children: [] | ||||
|   } | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|   imports: [RouterModule.forRoot(routes)], | ||||
|   exports: [RouterModule] | ||||
| }) | ||||
| export class AppRoutingModule { | ||||
| } | ||||
|  | @ -1,17 +1,9 @@ | |||
| <div *ngFor="let step of steps" class="inputwrapper"> | ||||
|   <div class="textwrapper arrow_box"> | ||||
|     <textarea class="textinput" (keyup)="update(step)" placeholder="Please enter your input ..." | ||||
|               [(ngModel)]="step.content">{{step.content}}</textarea> | ||||
|   </div> | ||||
|   <div [ngClass]="{selectwrapper: true, error: step.error}"> | ||||
|     <div class="arrow_box"> | ||||
|       <select class="select" (change)="convert(step, $event)"> | ||||
|         <option id="undefined">Select conversion ...</option> | ||||
|         <option class="option" *ngFor="let c of converters" id="{{c.getId()}}">{{c.getDisplayname()}} | ||||
|         </option> | ||||
|       </select> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="errormessage" *ngIf="step.error" [innerHTML]="step.message"></div> | ||||
| @for (step of steps; track step.index) { | ||||
|   <div class="inputwrapper"> | ||||
|     <app-text-input-field [step]="step" #ti></app-text-input-field> | ||||
|     <app-converter-selector [step]="step" [textInput]="ti"></app-converter-selector> | ||||
|     <app-error-message [step]="step"></app-error-message> | ||||
|   </div> | ||||
| } | ||||
| <app-version></app-version> | ||||
| <!--<router-outlet></router-outlet>--> | ||||
|  |  | |||
|  | @ -2,106 +2,3 @@ | |||
|   font-family: "ABeeZee", sans-serif; | ||||
|   margin: 0 1em 1em 1em; | ||||
| } | ||||
| 
 | ||||
| .textwrapper { | ||||
|   margin: 0 0 1em 0; | ||||
|   padding: 0 1em 0 0; | ||||
| } | ||||
| 
 | ||||
| .arrow_box { | ||||
|   position: relative; | ||||
|   background: #fff; | ||||
|   border: 1px solid #aaa; | ||||
|   &:focus { | ||||
|     border-color: #888; | ||||
|   } | ||||
|   &:hover { | ||||
|     border-color: #333; | ||||
|   } | ||||
|   &:after, &:before { | ||||
|     top: 100%; | ||||
|     left: 50%; | ||||
|     border: solid transparent; | ||||
|     content: " "; | ||||
|     height: 0; | ||||
|     width: 0; | ||||
|     position: absolute; | ||||
|     pointer-events: none; | ||||
|   } | ||||
|   &:after { | ||||
|     border-color: rgba(255, 255, 255, 0); | ||||
|     border-top-color: #fff; | ||||
|     border-width: 1em; | ||||
|     margin-left: -1em; | ||||
|   } | ||||
|   &:before { | ||||
|     border-color: rgba(170, 170, 170, 0); | ||||
|     border-top-color: #aaa; | ||||
|     border-width: calc(1em + 1px); | ||||
|     margin-left: calc(-1em - 1px); | ||||
|   } | ||||
|   &:focus:before { | ||||
|     border-color: rgba(136, 136, 136, 0); | ||||
|     border-top-color: #888; | ||||
|   } | ||||
|   &:hover:before { | ||||
|     border-color: rgba(51, 51, 51, 0); | ||||
|     border-top-color: #333; | ||||
|   } | ||||
|   .selectwrapper > & { | ||||
|     display: inline-block; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .textinput { | ||||
|   background-color: #fff; | ||||
|   border: none; | ||||
|   color: #000; | ||||
|   font-family: "Free Monospaced", monospace; | ||||
|   height: 10em; | ||||
|   margin: 0; | ||||
|   padding: 0.5em; | ||||
|   resize: vertical; | ||||
|   width: 100%; | ||||
|   &:focus { | ||||
|     border-color: #888; | ||||
|   } | ||||
|   &:hover { | ||||
|     border-color: #333; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .selectwrapper { | ||||
|   margin: 0 0 1em 0; | ||||
|   padding: 0; | ||||
|   text-align: center; | ||||
|   &.error { | ||||
|     > .arrow_box { | ||||
|       border-color: red; | ||||
|       &:before { | ||||
|         border-top-color: red; | ||||
|       } | ||||
|     } | ||||
|     select { | ||||
|       color: red; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .select { | ||||
|   background-color: #fff; | ||||
|   border: none; | ||||
|   color: #000; | ||||
|   font-family: "ABeeZee", sans-serif; | ||||
|   margin: 0; | ||||
|   padding: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .option { | ||||
|   /*    font-family: "ABeeZee", sans-serif;*/ | ||||
| } | ||||
| 
 | ||||
| .errormessage { | ||||
|   color: red; | ||||
|   text-align: center; | ||||
| } | ||||
|  |  | |||
|  | @ -1,53 +1,26 @@ | |||
| import {AppComponent} from './app.component'; | ||||
| import {async, ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| import {FormsModule} from '@angular/forms'; | ||||
| import {InputComponentManagerService} from './inputcomponentmanager.service'; | ||||
| import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| import {Step} from './step'; | ||||
| import {ConverterRegistryService} from './converterregistry.service'; | ||||
| import {Converter} from './converter/converter'; | ||||
| 
 | ||||
| describe('AppComponent', () => { | ||||
|   let sut: AppComponent; | ||||
|   let fixture: ComponentFixture<AppComponent>; | ||||
|   const firstStep: Step = new Step(0); | ||||
| 
 | ||||
|   const inputComponentManagerServiceStub = { | ||||
|     getFirst: () => { | ||||
|       return firstStep; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const converterRegistryServiceStub = { | ||||
|     getAllConverters: (): Converter[] => { | ||||
|       return []; | ||||
|     }, | ||||
| 
 | ||||
|     getConverter: (id: string): Converter => { | ||||
|       return undefined; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   beforeEach(async(() => { | ||||
|     /*return */ | ||||
|     TestBed.configureTestingModule({ | ||||
|       declarations: [AppComponent], | ||||
|       imports: [FormsModule], | ||||
|       providers: [ | ||||
|         {provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub}, | ||||
|         {provide: ConverterRegistryService, useValue: converterRegistryServiceStub} | ||||
|       ] | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [AppComponent] | ||||
|     }) | ||||
|       .compileComponents(); | ||||
|   })); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(AppComponent); | ||||
|     sut = fixture.componentInstance; | ||||
|   }); | ||||
| 
 | ||||
|   it('should create the app', async(() => { | ||||
|   it('should create the app', () => { | ||||
|     // const fixture = TestBed.createComponent(AppComponent);
 | ||||
|     const app = fixture.debugElement.componentInstance; | ||||
|     expect(app).toBeTruthy(); | ||||
|   })); | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -1,57 +1,32 @@ | |||
| import {Component, OnInit} from '@angular/core'; | ||||
| import {ConverterRegistryService} from './converterregistry.service'; | ||||
| import {InputComponentManagerService} from './inputcomponentmanager.service'; | ||||
| import {InputComponentManagerService} from './input-component-manager.service'; | ||||
| import {Step} from './step'; | ||||
| import {Converter} from './converter/converter'; | ||||
| import {RouterOutlet} from '@angular/router'; | ||||
| import {TextInputFieldComponent} from './text-input-field/text-input-field.component'; | ||||
| import {ConverterSelectorComponent} from './converter-selector/converter-selector.component'; | ||||
| import {ErrorMessageComponent} from './error-message/error-message.component'; | ||||
| import {VersionComponent} from './version/version.component'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-root', | ||||
|   standalone: true, | ||||
|   imports: [ | ||||
|     ConverterSelectorComponent, | ||||
|     ErrorMessageComponent, | ||||
|     RouterOutlet, | ||||
|     TextInputFieldComponent, | ||||
|     VersionComponent | ||||
|   ], | ||||
|   templateUrl: './app.component.html', | ||||
|   styleUrls: ['./app.component.scss'] | ||||
|   styleUrl: './app.component.scss' | ||||
| }) | ||||
| export class AppComponent implements OnInit { | ||||
|   public steps: Step[] = []; | ||||
|   public converters: Converter[] = []; | ||||
| 
 | ||||
|   constructor(private converterRegistryService: ConverterRegistryService, | ||||
|               private inputComponentManagerService: InputComponentManagerService) { | ||||
|   } | ||||
| 
 | ||||
|   convert(step: Step, $event: any): void { | ||||
|     step.selectedConverter = this.converterRegistryService.getConverter($event.target.selectedOptions[0].id); | ||||
|     this.update(step); | ||||
|   } | ||||
| 
 | ||||
|   update(step: Step): void { | ||||
|     const converter: Converter = step.selectedConverter; | ||||
| 
 | ||||
|     if (converter !== undefined) { | ||||
|       const content: string = step.content; | ||||
|       let result: string; | ||||
|       try { | ||||
|         result = converter.convert(content); | ||||
|       } catch (error) { | ||||
|         if (typeof console === 'object' && typeof console.log === 'function') { | ||||
|           console.log(error); | ||||
|         } | ||||
|         step.message = error.message; | ||||
|         step.error = true; | ||||
|         result = null; | ||||
|       } | ||||
|       if (result !== null) { | ||||
|         step.message = ''; | ||||
|         step.error = false; | ||||
|         if (result !== '') { | ||||
|           const nextComponent: Step = this.inputComponentManagerService.getNext(step); | ||||
|           nextComponent.content = result; | ||||
|           this.update(nextComponent); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   constructor(private inputComponentManagerService: InputComponentManagerService) { | ||||
|   } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.converters = this.converterRegistryService.getAllConverters(); | ||||
|     this.steps = this.inputComponentManagerService.getAllComponents(); | ||||
|     this.inputComponentManagerService.getFirst(); | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										8
									
								
								src/app/app.config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/app/app.config.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| import { ApplicationConfig } from '@angular/core'; | ||||
| import { provideRouter } from '@angular/router'; | ||||
| 
 | ||||
| import { routes } from './app.routes'; | ||||
| 
 | ||||
| export const appConfig: ApplicationConfig = { | ||||
|   providers: [provideRouter(routes)] | ||||
| }; | ||||
|  | @ -1,21 +0,0 @@ | |||
| import {BrowserModule} from '@angular/platform-browser'; | ||||
| import {NgModule} from '@angular/core'; | ||||
| import {AppComponent} from './app.component'; | ||||
| import {ConverterRegistryService} from './converterregistry.service'; | ||||
| import {InputComponentManagerService} from './inputcomponentmanager.service'; | ||||
| import {NativeLibraryWrapperService} from './nativelibrarywrapper.service'; | ||||
| import {FormsModule} from '@angular/forms'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
|     AppComponent | ||||
|   ], | ||||
|   imports: [ | ||||
|     BrowserModule, | ||||
|     FormsModule | ||||
|   ], | ||||
|   providers: [ConverterRegistryService, InputComponentManagerService, NativeLibraryWrapperService], | ||||
|   bootstrap: [AppComponent] | ||||
| }) | ||||
| export class AppModule { | ||||
| } | ||||
							
								
								
									
										3
									
								
								src/app/app.routes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/app/app.routes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| import { Routes } from '@angular/router'; | ||||
| 
 | ||||
| export const routes: Routes = []; | ||||
							
								
								
									
										40
									
								
								src/app/converter-registry.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/app/converter-registry.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | |||
| import {inject, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {ConverterRegistryService} from './converter-registry.service'; | ||||
| import {NativeLibraryWrapperService} from './native-library-wrapper.service'; | ||||
| import {Converter} from './converter/converter'; | ||||
| import {Base64Decoder} from './converter/base64-decoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| 
 | ||||
| describe('ConverterRegistryService', () => { | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       providers: [ | ||||
|         ConverterRegistryService, | ||||
|         {provide: NativeLibraryWrapperService, useValue: createSpyObj(['punycode', 'quotedPrintable', 'utf8'])} | ||||
|       ] | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('should be created', inject([ConverterRegistryService], (service: ConverterRegistryService) => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should register converters upon creation', inject([ConverterRegistryService], (service: ConverterRegistryService) => { | ||||
|     expect(service.getAllConverters()).toBeTruthy(); | ||||
|     expect(service.getAllConverters().length).toBeGreaterThan(0); | ||||
|   })); | ||||
| 
 | ||||
|   it('must not allow the same converter ID to be regisgered more than once', | ||||
|     inject([ConverterRegistryService], (service: ConverterRegistryService) => { | ||||
|       // arrange
 | ||||
|       const duplicateConverter: Converter = new Base64Decoder(); | ||||
|       const duplicateConverterId = duplicateConverter.getId(); | ||||
|       expect(() => { | ||||
|         // act
 | ||||
|         (service as any).registerConverter(duplicateConverter); | ||||
|       }) | ||||
|       // assert
 | ||||
|         .toThrowError(`Converter-ID ${duplicateConverterId} is already registered!`); | ||||
|     })); | ||||
| }); | ||||
|  | @ -1,27 +1,29 @@ | |||
| import {Injectable} from '@angular/core'; | ||||
| import {Base64Decoder} from './converter/base64-decoder'; | ||||
| import {Base64Encoder} from './converter/base64-encoder'; | ||||
| import {BinToDecConverter} from './converter/bin-to-dec-converter'; | ||||
| import {Converter} from './converter/converter'; | ||||
| import {Base64Encoder} from './converter/base64encoder'; | ||||
| import {Base64Decoder} from './converter/base64decoder'; | ||||
| import {URIEncoder} from './converter/uriencoder'; | ||||
| import {DecToBinConverter} from './converter/dec-to-bin-converter'; | ||||
| import {DecToHexConverter} from './converter/dec-to-hex-converter'; | ||||
| import {HexToDecConverter} from './converter/hex-to-dec-converter'; | ||||
| import {HTMLEntitiesDecoder} from './converter/htmlentities-decoder'; | ||||
| import {HTMLEntitiesEncoder} from './converter/htmlentities-encoder'; | ||||
| import {Injectable} from '@angular/core'; | ||||
| import {NativeLibraryWrapperService} from './native-library-wrapper.service'; | ||||
| import {PunycodeDecoder} from './converter/punycode-decoder'; | ||||
| import {PunycodeEncoder} from './converter/punycode-encoder'; | ||||
| import {QuotedPrintableDecoder} from './converter/quoted-printable-decoder'; | ||||
| import {QuotedPrintableEncoder} from './converter/quoted-printable-encoder'; | ||||
| import {ROT13Converter} from './converter/rot13-converter'; | ||||
| import {URIComponentDecoder} from './converter/uricomponent-decoder'; | ||||
| import {URIComponentEncoder} from './converter/uricomponent-encoder'; | ||||
| import {URIDecoder} from './converter/uridecoder'; | ||||
| import {URIComponentEncoder} from './converter/uricomponentencoder'; | ||||
| import {URIComponentDecoder} from './converter/uricomponentdecoder'; | ||||
| import {HTMLEntitiesEncoder} from './converter/htmlentitiesencoder'; | ||||
| import {HTMLEntitiesDecoder} from './converter/htmlentitiesdecoder'; | ||||
| import {DecToHexConverter} from './converter/dectohexconverter'; | ||||
| import {HexToDecConverter} from './converter/hextodecconverter'; | ||||
| import {DecToBinConverter} from './converter/dectobinconverter'; | ||||
| import {BinToDecConverter} from './converter/bintodecconverter'; | ||||
| import {QuotedPrintableDecoder} from './converter/quotedprintabledecoder'; | ||||
| import {QuotedPrintableEncoder} from './converter/quotedprintableencoder'; | ||||
| import {NativeLibraryWrapperService} from './nativelibrarywrapper.service'; | ||||
| import {PunycodeEncoder} from './converter/punycodeencoder'; | ||||
| import {PunycodeDecoder} from './converter/punycodedecoder'; | ||||
| import {UTF8Encoder} from './converter/utf8encoder'; | ||||
| import {UTF8Decoder} from './converter/utf8decoder'; | ||||
| import {ROT13Converter} from './converter/rot13converter'; | ||||
| import {URIEncoder} from './converter/uriencoder'; | ||||
| import {UTF8Decoder} from './converter/utf8-decoder'; | ||||
| import {UTF8Encoder} from './converter/utf8-encoder'; | ||||
| 
 | ||||
| @Injectable() | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class ConverterRegistryService { | ||||
|   private converters: Converter[] = []; | ||||
| 
 | ||||
|  | @ -33,13 +35,8 @@ export class ConverterRegistryService { | |||
|     return this.converters; | ||||
|   } | ||||
| 
 | ||||
|   public getConverter(id: string): Converter { | ||||
|     for (let i = 0; i < this.converters.length; i++) { | ||||
|       if (this.converters[i].getId() === id) { | ||||
|         return this.converters[i]; | ||||
|       } | ||||
|     } | ||||
|     return undefined; | ||||
|   public getConverter(id: string): Converter | undefined { | ||||
|     return this.converters.find((converter: Converter): boolean => converter.getId() === id); | ||||
|   } | ||||
| 
 | ||||
|   private init(): void { | ||||
|  | @ -65,11 +62,11 @@ export class ConverterRegistryService { | |||
|   } | ||||
| 
 | ||||
|   private registerConverter(converter: Converter): void { | ||||
|     this.converters.forEach((c: Converter) => { | ||||
|       if (c.getId() === converter.getId()) { | ||||
|         throw new Error('Converter-ID ' + converter.getId() + ' is already registered!'); | ||||
|     // Don't allow duplicate registration of the same converter id
 | ||||
|     if (this.converters.some((c: Converter): boolean => c.getId() === converter.getId())) { | ||||
|       throw new Error(`Converter-ID ${converter.getId()} is already registered!`); | ||||
|     } | ||||
|     }); | ||||
| 
 | ||||
|     this.converters.push(converter); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/app/converter-selector/converter-selector.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/app/converter-selector/converter-selector.component.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| <div [ngClass]="{selectwrapper: true, error: step.error}"> | ||||
|   <div class="arrow_box"> | ||||
|     <select class="select" (change)="convert($event)"> | ||||
|       <option id="undefined">Select conversion ...</option> | ||||
|       @for (c of converters; track c.getId()) { | ||||
|         <option class="option" id="{{c.getId()}}">{{ c.getDisplayname() }}</option> | ||||
|       } | ||||
|     </select> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										74
									
								
								src/app/converter-selector/converter-selector.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/app/converter-selector/converter-selector.component.scss
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| .selectwrapper { | ||||
|   margin: 0 0 1em 0; | ||||
|   padding: 0; | ||||
|   text-align: center; | ||||
|   &.error { | ||||
|     > .arrow_box { | ||||
|       border-color: red; | ||||
|       &:before { | ||||
|         border-top-color: red; | ||||
|       } | ||||
|     } | ||||
|     select { | ||||
|       color: red; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .select { | ||||
|   background-color: #fff; | ||||
|   border: none; | ||||
|   color: #000; | ||||
|   font-family: "ABeeZee", sans-serif; | ||||
|   margin: 0; | ||||
|   padding: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .arrow_box { | ||||
|   position: relative; | ||||
|   background: #fff; | ||||
|   border: 1px solid #aaa; | ||||
|   &:focus { | ||||
|     border-color: #888; | ||||
|   } | ||||
|   &:hover { | ||||
|     border-color: #333; | ||||
|   } | ||||
|   &:after, &:before { | ||||
|     top: 100%; | ||||
|     left: 50%; | ||||
|     border: solid transparent; | ||||
|     content: " "; | ||||
|     height: 0; | ||||
|     width: 0; | ||||
|     position: absolute; | ||||
|     pointer-events: none; | ||||
|   } | ||||
|   &:after { | ||||
|     border-color: rgba(255, 255, 255, 0); | ||||
|     border-top-color: #fff; | ||||
|     border-width: 1em; | ||||
|     margin-left: -1em; | ||||
|   } | ||||
|   &:before { | ||||
|     border-color: rgba(170, 170, 170, 0); | ||||
|     border-top-color: #aaa; | ||||
|     border-width: calc(1em + 1px); | ||||
|     margin-left: calc(-1em - 1px); | ||||
|   } | ||||
|   &:focus:before { | ||||
|     border-color: rgba(136, 136, 136, 0); | ||||
|     border-top-color: #888; | ||||
|   } | ||||
|   &:hover:before { | ||||
|     border-color: rgba(51, 51, 51, 0); | ||||
|     border-top-color: #333; | ||||
|   } | ||||
|   .selectwrapper > & { | ||||
|     display: inline-block; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .option { | ||||
|   /*    font-family: "ABeeZee", sans-serif;*/ | ||||
| } | ||||
|  | @ -0,0 +1,93 @@ | |||
| import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {ConverterSelectorComponent} from './converter-selector.component'; | ||||
| import {Component, ViewChild} from '@angular/core'; | ||||
| import {Step} from '../step'; | ||||
| import {TextInputFieldComponent} from '../text-input-field/text-input-field.component'; | ||||
| import {InputComponentManagerService} from '../input-component-manager.service'; | ||||
| import {ConverterRegistryService} from '../converter-registry.service'; | ||||
| import {Converter} from '../converter/converter'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import SpyObj = jasmine.SpyObj; | ||||
| 
 | ||||
| @Component({ | ||||
|   template: '<app-converter-selector [step]="step" [textInput]="textInputComponent"></app-converter-selector>' | ||||
| }) | ||||
| class TestHostComponent { | ||||
|   public step: Step = new Step(42); | ||||
|   public textInputComponent: TextInputFieldComponent = new TextInputFieldComponent(inputComponentManagerServiceStub); | ||||
|   @ViewChild(ConverterSelectorComponent) | ||||
|   public sutComponent!: ConverterSelectorComponent; | ||||
| } | ||||
| 
 | ||||
| const inputComponentManagerServiceStub = createSpyObj(['getNext']); | ||||
| 
 | ||||
| describe('ConverterSelectorComponent', () => { | ||||
|   let testHostComponent: TestHostComponent; | ||||
|   let testHostFixture: ComponentFixture<TestHostComponent>; | ||||
| 
 | ||||
|   const converter1: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']); | ||||
|   const converter2: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']); | ||||
|   const converter3: SpyObj<Converter> = createSpyObj(['getId', 'getDisplayname', 'convert']); | ||||
|   // converter1.getId.and.returnValue('converter1');
 | ||||
|   converter1.getDisplayname.and.returnValue('Converter 1'); | ||||
|   // converter2.getId.and.returnValue('converter2');
 | ||||
|   converter2.getDisplayname.and.returnValue('Converter 2'); | ||||
|   // converter3.getId.and.returnValue('converter3');
 | ||||
|   converter3.getDisplayname.and.returnValue('Converter 3'); | ||||
|   const converterRegistryServiceStub = createSpyObj(['getAllConverters', 'getConverter']); | ||||
|   converterRegistryServiceStub.getAllConverters.and.returnValue([converter1, converter2, converter3]); | ||||
|   converterRegistryServiceStub.getConverter.and.returnValue(undefined); | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [ConverterSelectorComponent], | ||||
|       declarations: [ | ||||
|         TestHostComponent | ||||
|       ], | ||||
|       providers: [ | ||||
|         {provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub}, | ||||
|         {provide: ConverterRegistryService, useValue: converterRegistryServiceStub} | ||||
|       ] | ||||
|     }) | ||||
|       .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     testHostFixture = TestBed.createComponent(TestHostComponent); | ||||
|     testHostComponent = testHostFixture.componentInstance; | ||||
|     testHostFixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create the component', () => { | ||||
|     expect(testHostComponent.sutComponent).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should update the conversion when the selection changes', () => { | ||||
|     // arrange
 | ||||
|     // set next step
 | ||||
|     const nextStep: Step = new Step(99); | ||||
|     inputComponentManagerServiceStub.getNext.and.returnValue(nextStep); | ||||
|     // set up converter
 | ||||
|     const dummyConverter = createSpyObj(['convert']); | ||||
|     dummyConverter.convert.and.returnValue('Converted value'); | ||||
|     converterRegistryServiceStub.getConverter.and.returnValue(dummyConverter); | ||||
|     // create event structure
 | ||||
|     const event = {target: {selectedOptions: [{id: 'The testing converter id'}]}}; | ||||
| 
 | ||||
|     // act
 | ||||
|     testHostComponent.sutComponent.convert(event); | ||||
| 
 | ||||
|     // assert
 | ||||
|     expect(converterRegistryServiceStub.getConverter).toHaveBeenCalledWith('The testing converter id'); | ||||
|     expect(nextStep.content).toEqual('Converted value'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should load available converters upon creation', () => { | ||||
|     const converters: Converter[] = testHostComponent.sutComponent.converters; | ||||
|     expect(converters.length).toEqual(3); | ||||
|     expect(converters[0]).toEqual(converter1); | ||||
|     expect(converters[1]).toEqual(converter2); | ||||
|     expect(converters[2]).toEqual(converter3); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										33
									
								
								src/app/converter-selector/converter-selector.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/app/converter-selector/converter-selector.component.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| import {Component, Input, OnInit} from '@angular/core'; | ||||
| import {Step} from '../step'; | ||||
| import {Converter} from '../converter/converter'; | ||||
| import {ConverterRegistryService} from '../converter-registry.service'; | ||||
| import {TextInputFieldComponent} from '../text-input-field/text-input-field.component'; | ||||
| import {CommonModule} from '@angular/common'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-converter-selector', | ||||
|   templateUrl: './converter-selector.component.html', | ||||
|   standalone: true, | ||||
|   styleUrls: ['./converter-selector.component.scss'], | ||||
|   imports: [CommonModule] | ||||
| }) | ||||
| export class ConverterSelectorComponent implements OnInit { | ||||
|   @Input() | ||||
|   public step!: Step; | ||||
|   @Input() | ||||
|   textInput!: TextInputFieldComponent; | ||||
|   public converters: Converter[] = []; | ||||
| 
 | ||||
|   constructor(private converterRegistryService: ConverterRegistryService) { | ||||
|   } | ||||
| 
 | ||||
|   convert($event: any): void { | ||||
|     this.step.selectedConverter = this.converterRegistryService.getConverter($event.target.selectedOptions[0].id); | ||||
|     this.textInput.update(this.step); | ||||
|   } | ||||
| 
 | ||||
|   ngOnInit() { | ||||
|     this.converters = this.converterRegistryService.getAllConverters(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/app/converter/base64-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/base64-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {Base64Decoder} from './base64-decoder'; | ||||
| 
 | ||||
| describe('Base64Decoder', () => { | ||||
|   let sut: Base64Decoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new Base64Decoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "base64decode"', () => { | ||||
|     expect(sut.getId()).toEqual('base64decode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should decode "SGVsbG8sIFdvcmxkIQ==" to "Hello, World!"', () => { | ||||
|     expect(sut.convert('SGVsbG8sIFdvcmxkIQ==')).toEqual('Hello, World!'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('foo bar.')).toThrowError('Could not decode base64 string. Maybe corrupt input?'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										27
									
								
								src/app/converter/base64-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/base64-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {Base64Encoder} from './base64-encoder'; | ||||
| 
 | ||||
| describe('Base64Encoder', () => { | ||||
|   let sut: Base64Encoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new Base64Encoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "base64encode"', () => { | ||||
|     expect(sut.getId()).toEqual('base64encode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should encode "Oh, guete Tag!" to "T2gsIGd1ZXRlIFRhZyE="', () => { | ||||
|     expect(sut.convert('Oh, guete Tag!')).toEqual('T2gsIGd1ZXRlIFRhZyE='); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('€')).toThrowError(/Looks like you've got a character outside of the Latin1 range there./); | ||||
|   }); | ||||
| }); | ||||
|  | @ -14,7 +14,7 @@ export class Base64Encoder implements Converter { | |||
|       return btoa(input); | ||||
|     } catch (exception) { | ||||
|       console.error(exception); | ||||
|       throw new Error('Ouch! Looks like you\'ve got a UTF-8 character there. Too bad, this is not supported yet. ' | ||||
|       throw new Error('Ouch! Looks like you\'ve got a character outside of the Latin1 range there. Too bad, this is not supported yet. ' | ||||
|         + 'We\'re working on it and hope to be ready soon! Why don\'t you ' | ||||
|         + '<a href="https://duckduckgo.com/?q=cute+kitties&iar=images">enjoy some kittens</a> meanwhile?'); | ||||
|     } | ||||
							
								
								
									
										27
									
								
								src/app/converter/bin-to-dec-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/bin-to-dec-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {BinToDecConverter} from './bin-to-dec-converter'; | ||||
| 
 | ||||
| describe('BinToDecConverter', () => { | ||||
|   let sut: BinToDecConverter; | ||||
| 
 | ||||
|   beforeEach(() => sut = new BinToDecConverter()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "bintodec"', () => { | ||||
|     expect(sut.getId()).toEqual('bintodec'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should convert "11011" to "27"', () => { | ||||
|     expect(sut.convert('11011')).toEqual('27'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('1foo bar')).toThrowError('The input seems not to be a valid binary number.'); | ||||
|   }); | ||||
| }); | ||||
|  | @ -11,7 +11,7 @@ export class BinToDecConverter implements Converter { | |||
| 
 | ||||
|   convert(input: string): string { | ||||
|     const n: number = parseInt(input, 2); | ||||
|     if (isNaN(n)) { | ||||
|     if (isNaN(n) || !input.trim().match(/^([01]+)$/)) { | ||||
|       throw new Error('The input seems not to be a valid binary number.'); | ||||
|     } | ||||
|     return n.toString(10); | ||||
							
								
								
									
										27
									
								
								src/app/converter/dec-to-bin-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/dec-to-bin-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {DecToBinConverter} from './dec-to-bin-converter'; | ||||
| 
 | ||||
| describe('DecToBinConverter', () => { | ||||
|   let sut: DecToBinConverter; | ||||
| 
 | ||||
|   beforeEach(() => sut = new DecToBinConverter()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "dectobin"', () => { | ||||
|     expect(sut.getId()).toEqual('dectobin'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should convert "22" to "10110"', () => { | ||||
|     expect(sut.convert('22')).toEqual('10110'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid integer.'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										27
									
								
								src/app/converter/dec-to-hex-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/dec-to-hex-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {DecToHexConverter} from './dec-to-hex-converter'; | ||||
| 
 | ||||
| describe('DecToHexConverter', () => { | ||||
|   let sut: DecToHexConverter; | ||||
| 
 | ||||
|   beforeEach(() => sut = new DecToHexConverter()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "dectohex"', () => { | ||||
|     expect(sut.getId()).toEqual('dectohex'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should convert "22" to "16"', () => { | ||||
|     expect(sut.convert('22')).toEqual('16'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid integer.'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										27
									
								
								src/app/converter/hex-to-dec-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/converter/hex-to-dec-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import {HexToDecConverter} from './hex-to-dec-converter'; | ||||
| 
 | ||||
| describe('HexToDecConverter', () => { | ||||
|   let sut: HexToDecConverter; | ||||
| 
 | ||||
|   beforeEach(() => sut = new HexToDecConverter()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "hextodec"', () => { | ||||
|     expect(sut.getId()).toEqual('hextodec'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should convert "ab" to "171"', () => { | ||||
|     expect(sut.convert('ab')).toEqual('171'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should raise an exception on invalid input', () => { | ||||
|     expect(() => sut.convert('foo bar')).toThrowError('The input seems not to be a valid hexadecimal number.'); | ||||
|   }); | ||||
| }); | ||||
|  | @ -11,7 +11,7 @@ export class HexToDecConverter implements Converter { | |||
| 
 | ||||
|   convert(input: string): string { | ||||
|     const n: number = parseInt(input, 16); | ||||
|     if (isNaN(n)) { | ||||
|     if (isNaN(n) || !input.trim().match(/^((0x|0X)?[0-9a-fA-F]+)$/)) { | ||||
|       throw new Error('The input seems not to be a valid hexadecimal number.'); | ||||
|     } | ||||
|     return n.toString(10); | ||||
							
								
								
									
										24
									
								
								src/app/converter/htmlentities-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/app/converter/htmlentities-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| import {HTMLEntitiesDecoder} from './htmlentities-decoder'; | ||||
| 
 | ||||
| describe('HTMLEntitiesDecoder', () => { | ||||
|   let sut: HTMLEntitiesDecoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new HTMLEntitiesDecoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "decodehtmlentities"', () => { | ||||
|     expect(sut.getId()).toEqual('decodehtmlentities'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should decode "<span>"Hi" & "Lo"</span>" to "<span>"Hi" & "Lo"</span>"', () => { | ||||
|     expect(sut.convert('<span>"Hi" & "Lo"</span>')) | ||||
|       .toEqual('<span>"Hi" & "Lo"</span>'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										24
									
								
								src/app/converter/htmlentities-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/app/converter/htmlentities-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| import {HTMLEntitiesEncoder} from './htmlentities-encoder'; | ||||
| 
 | ||||
| describe('HTMLEntitiesEncoder', () => { | ||||
|   let sut: HTMLEntitiesEncoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new HTMLEntitiesEncoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "encodehtmlentities"', () => { | ||||
|     expect(sut.getId()).toEqual('encodehtmlentities'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should encode "<span>"Hi" & "Lo"</span>" to "<span>"Hi" & "Lo"</span>"', () => { | ||||
|     expect(sut.convert('<span>"Hi" & "Lo"</span>')) | ||||
|       .toEqual('<span>"Hi" & "Lo"</span>'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										37
									
								
								src/app/converter/punycode-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/punycode-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {PunycodeDecoder} from './punycode-decoder'; | ||||
| import {NativeLibraryWrapperService, Punycode} from '../native-library-wrapper.service'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('PunycodeDecoder', () => { | ||||
|   let sut: PunycodeDecoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let decodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {punycode: createSpyObj<Punycode>(['decode'])}; | ||||
|     decodeSpy = nativeWrapperSpy.punycode!.decode as Spy; | ||||
|     sut = new PunycodeDecoder((nativeWrapperSpy as NativeLibraryWrapperService)); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "decodepunycode"', () => { | ||||
|     expect(sut.getId()).toEqual('decodepunycode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native punycode decoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     decodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(decodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class PunycodeDecoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										37
									
								
								src/app/converter/punycode-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/punycode-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {NativeLibraryWrapperService, Punycode} from '../native-library-wrapper.service'; | ||||
| import {PunycodeEncoder} from './punycode-encoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('PunycodeEncoder', () => { | ||||
|   let sut: PunycodeEncoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let encodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {punycode: createSpyObj<Punycode>(['encode'])}; | ||||
|     encodeSpy = nativeWrapperSpy.punycode!.encode as Spy; | ||||
|     sut = new PunycodeEncoder(nativeWrapperSpy as NativeLibraryWrapperService); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "encodepunycode"', () => { | ||||
|     expect(sut.getId()).toEqual('encodepunycode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native punycode encoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     encodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(encodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class PunycodeEncoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										37
									
								
								src/app/converter/quoted-printable-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/quoted-printable-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {NativeLibraryWrapperService, QuotedPrintable} from '../native-library-wrapper.service'; | ||||
| import {QuotedPrintableDecoder} from './quoted-printable-decoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('QuotedPrintableDecoder', () => { | ||||
|   let sut: QuotedPrintableDecoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let decodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {quotedPrintable: createSpyObj<QuotedPrintable>(['decode'])}; | ||||
|     decodeSpy = nativeWrapperSpy.quotedPrintable!.decode as Spy; | ||||
|     sut = new QuotedPrintableDecoder((nativeWrapperSpy as NativeLibraryWrapperService)); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "decodequotedprintable"', () => { | ||||
|     expect(sut.getId()).toEqual('decodequotedprintable'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native quoted printable decoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     decodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(decodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class QuotedPrintableDecoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										37
									
								
								src/app/converter/quoted-printable-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/quoted-printable-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {NativeLibraryWrapperService, QuotedPrintable} from '../native-library-wrapper.service'; | ||||
| import {QuotedPrintableEncoder} from './quoted-printable-encoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('QuotedPrintableEncoder', () => { | ||||
|   let sut: QuotedPrintableEncoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let encodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {quotedPrintable: createSpyObj<QuotedPrintable>(['encode'])}; | ||||
|     encodeSpy = nativeWrapperSpy.quotedPrintable!.encode as Spy; | ||||
|     sut = new QuotedPrintableEncoder(nativeWrapperSpy as NativeLibraryWrapperService); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "encodequotedprintable"', () => { | ||||
|     expect(sut.getId()).toEqual('encodequotedprintable'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native quoted printable encoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     encodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(encodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class QuotedPrintableEncoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										28
									
								
								src/app/converter/rot13-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/app/converter/rot13-converter.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| import {ROT13Converter} from './rot13-converter'; | ||||
| 
 | ||||
| describe('ROT13Converter', () => { | ||||
|   let sut: ROT13Converter; | ||||
| 
 | ||||
|   beforeEach(() => sut = new ROT13Converter()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "rot13"', () => { | ||||
|     expect(sut.getId()).toEqual('rot13'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should encode "Hello, World!" to "Uryyb, Jbeyq!"', () => { | ||||
|     expect(sut.convert('Hello, World!')).toEqual('Uryyb, Jbeyq!'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should return the original input after being applied twice', () => { | ||||
|     const input = 'Ok, so this string is just a bunch of letters. And numbers: 1, 2, 3. Ans others: /&%. Kthxbye!'; | ||||
|     expect(sut.convert(sut.convert(input))).toEqual(input); | ||||
|   }); | ||||
| }); | ||||
|  | @ -6,14 +6,14 @@ export class ROT13Converter implements Converter { | |||
|   } | ||||
| 
 | ||||
|   getId(): string { | ||||
|     return 'rot13convert'; | ||||
|     return 'rot13'; | ||||
|   } | ||||
| 
 | ||||
|   convert(input: string): string { | ||||
|     try { | ||||
|       const inChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; | ||||
|       const outChars = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'; | ||||
|       const translate = c => { | ||||
|       const translate = (c: string) => { | ||||
|         const charIndex = inChars.indexOf(c); | ||||
|         return charIndex > -1 ? outChars[charIndex] : c; | ||||
|       }; | ||||
							
								
								
									
										25
									
								
								src/app/converter/uricomponent-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/app/converter/uricomponent-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import {URIComponentDecoder} from './uricomponent-decoder'; | ||||
| 
 | ||||
| describe('URIComponentDecoder', () => { | ||||
|   let sut: URIComponentDecoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new URIComponentDecoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "uricomponentdecode"', () => { | ||||
|     expect(sut.getId()).toEqual('uricomponentdecode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should decode "http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya" ' + | ||||
|     'to "http://mänu:ghëim@host:port/hi.there?oh=well#ya"', () => { | ||||
|     expect(sut.convert('http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya')) | ||||
|       .toEqual('http://mänu:ghëim@host:port/hi.there?oh=well#ya'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										25
									
								
								src/app/converter/uricomponent-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/app/converter/uricomponent-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import {URIComponentEncoder} from './uricomponent-encoder'; | ||||
| 
 | ||||
| describe('URIComponentEncoder', () => { | ||||
|   let sut: URIComponentEncoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new URIComponentEncoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "uricomponentencode"', () => { | ||||
|     expect(sut.getId()).toEqual('uricomponentencode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should encode "http://mänu:ghëim@host:port/hi.there?oh=well#ya" ' + | ||||
|     'to "http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya"', () => { | ||||
|     expect(sut.convert('http://mänu:ghëim@host:port/hi.there?oh=well#ya')) | ||||
|       .toEqual('http%3A%2F%2Fm%C3%A4nu%3Agh%C3%ABim%40host%3Aport%2Fhi.there%3Foh%3Dwell%23ya'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										25
									
								
								src/app/converter/uridecoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/app/converter/uridecoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import {URIDecoder} from './uridecoder'; | ||||
| 
 | ||||
| describe('URIDecoder', () => { | ||||
|   let sut: URIDecoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new URIDecoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "uridecode"', () => { | ||||
|     expect(sut.getId()).toEqual('uridecode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should decode "http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya" ' + | ||||
|     'to "http://mänu:ghëim@host:port/hi.there?oh=well#ya"', () => { | ||||
|     expect(sut.convert('http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya')) | ||||
|       .toEqual('http://mänu:ghëim@host:port/hi.there?oh=well#ya'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										25
									
								
								src/app/converter/uriencoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/app/converter/uriencoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import {URIEncoder} from './uriencoder'; | ||||
| 
 | ||||
| describe('URIEncoder', () => { | ||||
|   let sut: URIEncoder; | ||||
| 
 | ||||
|   beforeEach(() => sut = new URIEncoder()); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "uriencode"', () => { | ||||
|     expect(sut.getId()).toEqual('uriencode'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should encode "http://mänu:ghëim@host:port/hi.there?oh=well#ya" ' + | ||||
|     'to "http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya"', () => { | ||||
|     expect(sut.convert('http://mänu:ghëim@host:port/hi.there?oh=well#ya')) | ||||
|       .toEqual('http://m%C3%A4nu:gh%C3%ABim@host:port/hi.there?oh=well#ya'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										37
									
								
								src/app/converter/utf8-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/utf8-decoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {NativeLibraryWrapperService, Utf8} from '../native-library-wrapper.service'; | ||||
| import {UTF8Decoder} from './utf8-decoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('UTF8Decoder', () => { | ||||
|   let sut: UTF8Decoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let decodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {utf8: createSpyObj<Utf8>(['decode'])}; | ||||
|     decodeSpy = nativeWrapperSpy.utf8!.decode as Spy; | ||||
|     sut = new UTF8Decoder((nativeWrapperSpy as NativeLibraryWrapperService)); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "decodeutf8"', () => { | ||||
|     expect(sut.getId()).toEqual('decodeutf8'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native UTF-8 decoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     decodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(decodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class UTF8Decoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										37
									
								
								src/app/converter/utf8-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/converter/utf8-encoder.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {NativeLibraryWrapperService, Utf8} from '../native-library-wrapper.service'; | ||||
| import {UTF8Encoder} from './utf8-encoder'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| import Spy = jasmine.Spy; | ||||
| 
 | ||||
| describe('UTF8Encoder', () => { | ||||
|   let sut: UTF8Encoder; | ||||
|   let nativeWrapperSpy: Partial<NativeLibraryWrapperService>; | ||||
|   let encodeSpy: Spy; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     nativeWrapperSpy = {utf8: createSpyObj<Utf8>(['encode'])}; | ||||
|     encodeSpy = nativeWrapperSpy.utf8!.encode as Spy; | ||||
|     sut = new UTF8Encoder(nativeWrapperSpy as NativeLibraryWrapperService); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create an instance', () => { | ||||
|     expect(sut).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have a display name', () => { | ||||
|     expect(sut.getDisplayname()).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the id "encodeutf8"', () => { | ||||
|     expect(sut.getId()).toEqual('encodeutf8'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should call through to the native UTF-8 encoder', () => { | ||||
|     const testInput = 'My input'; | ||||
|     const expectedOutput = 'It worked'; | ||||
|     encodeSpy.and.returnValue(expectedOutput); | ||||
|     const result: string = sut.convert(testInput); | ||||
|     expect(result).toEqual(expectedOutput); | ||||
|     expect(encodeSpy).toHaveBeenCalledWith(testInput); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import {Converter} from './converter'; | ||||
| import {NativeLibraryWrapperService} from '../nativelibrarywrapper.service'; | ||||
| import {NativeLibraryWrapperService} from '../native-library-wrapper.service'; | ||||
| 
 | ||||
| export class UTF8Encoder implements Converter { | ||||
| 
 | ||||
							
								
								
									
										3
									
								
								src/app/error-message/error-message.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/app/error-message/error-message.component.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| @if (step.error) { | ||||
|   <div class="errormessage" [innerHTML]="step.message"></div> | ||||
| } | ||||
							
								
								
									
										4
									
								
								src/app/error-message/error-message.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/app/error-message/error-message.component.scss
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| .errormessage { | ||||
|   color: red; | ||||
|   text-align: center; | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/app/error-message/error-message.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/app/error-message/error-message.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {ErrorMessageComponent} from './error-message.component'; | ||||
| import {Component, ViewChild} from '@angular/core'; | ||||
| import {Step} from '../step'; | ||||
| 
 | ||||
| @Component({ | ||||
|   template: '<app-error-message [step]="step"></app-error-message>' | ||||
| }) | ||||
| class TestHostComponent { | ||||
|   public step: Step = new Step(42); | ||||
|   @ViewChild(ErrorMessageComponent) | ||||
|   public sutComponent!: ErrorMessageComponent; | ||||
| } | ||||
| 
 | ||||
| describe('ErrorMessageComponent', () => { | ||||
|   let testHostComponent: TestHostComponent; | ||||
|   let testHostFixture: ComponentFixture<TestHostComponent>; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [ErrorMessageComponent], | ||||
|       declarations: [TestHostComponent] | ||||
|     }) | ||||
|       .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     testHostFixture = TestBed.createComponent(TestHostComponent); | ||||
|     testHostComponent = testHostFixture.componentInstance; | ||||
|     testHostFixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create', () => { | ||||
|     expect(testHostComponent.sutComponent).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should display an error message', () => { | ||||
|     // arrange
 | ||||
|     testHostComponent.step.error = true; | ||||
|     testHostComponent.step.message = 'This is an error message'; | ||||
| 
 | ||||
|     // act
 | ||||
|     testHostFixture.detectChanges(); | ||||
| 
 | ||||
|     // assert
 | ||||
|     expect(testHostComponent.sutComponent.step.error).toBeTruthy(); | ||||
|     expect(testHostComponent.sutComponent.step.message).toEqual('This is an error message'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										16
									
								
								src/app/error-message/error-message.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/app/error-message/error-message.component.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| import {Component, Input} from '@angular/core'; | ||||
| import {Step} from '../step'; | ||||
| 
 | ||||
| @Component({ | ||||
|     selector: 'app-error-message', | ||||
|     templateUrl: './error-message.component.html', | ||||
|     standalone: true, | ||||
|     styleUrls: ['./error-message.component.scss'] | ||||
| }) | ||||
| export class ErrorMessageComponent { | ||||
|   @Input() | ||||
|   public step!: Step; | ||||
| 
 | ||||
|   constructor() { | ||||
|   } | ||||
| } | ||||
							
								
								
									
										71
									
								
								src/app/input-component-manager.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/app/input-component-manager.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| import {inject, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {InputComponentManagerService} from './input-component-manager.service'; | ||||
| import {Step} from './step'; | ||||
| 
 | ||||
| describe('InputComponentManagerService', () => { | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       providers: [InputComponentManagerService] | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('should be created', inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should create a component if requesting the first one', | ||||
|     inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|       const firstStep: Step = service.getFirst(); | ||||
|       expect(firstStep).toBeTruthy(); | ||||
|       expect(service.getFirst()).toBe(firstStep); | ||||
|       expect(service.getAllComponents().length).toEqual(1); | ||||
|     }) | ||||
|   ); | ||||
| 
 | ||||
|   it('must not add a null Step', inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|     expect(() => service.register(null!)).toThrowError(); | ||||
|     expect(service.getAllComponents().length).toEqual(0); | ||||
|   })); | ||||
| 
 | ||||
|   it('must not add an undefined Step', inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|     expect(() => service.register(undefined!)).toThrowError(); | ||||
|     expect(service.getAllComponents().length).toEqual(0); | ||||
|   })); | ||||
| 
 | ||||
|   it('should register new Steps', inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|     service.register(new Step(99)); | ||||
|     expect(service.getAllComponents().length).toEqual(1); | ||||
|     service.register(new Step(100)); | ||||
|     expect(service.getAllComponents().length).toEqual(2); | ||||
|   })); | ||||
| 
 | ||||
|   it('should return a new step if the next step doesn\'t exist', | ||||
|     inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|       // arrange
 | ||||
|       const firstStep: Step = new Step(0); | ||||
|       service.register(firstStep); | ||||
| 
 | ||||
|       // act
 | ||||
|       const nextStep: Step = service.getNext(firstStep); | ||||
| 
 | ||||
|       // assert
 | ||||
|       expect(service.getAllComponents().length).toEqual(2); | ||||
|       expect(nextStep.index).toEqual(1); | ||||
|     }) | ||||
|   ); | ||||
| 
 | ||||
|   it('should return the next step if requested', inject([InputComponentManagerService], (service: InputComponentManagerService) => { | ||||
|     // arrange
 | ||||
|     const firstStep: Step = new Step(0); | ||||
|     const nextStep: Step = new Step(1); | ||||
|     service.register(firstStep); | ||||
|     service.register(nextStep); | ||||
| 
 | ||||
|     // act
 | ||||
|     const requestedStep: Step = service.getNext(firstStep); | ||||
|     // assert
 | ||||
|     expect(service.getAllComponents().length).toEqual(2); | ||||
|     expect(requestedStep).toEqual(nextStep); | ||||
|   })); | ||||
| }); | ||||
|  | @ -1,7 +1,9 @@ | |||
| import {Injectable} from '@angular/core'; | ||||
| import {Step} from './step'; | ||||
| 
 | ||||
| @Injectable() | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class InputComponentManagerService { | ||||
|   private components: Step[] = []; | ||||
| 
 | ||||
|  | @ -9,6 +11,9 @@ export class InputComponentManagerService { | |||
|   } | ||||
| 
 | ||||
|   public register(component: Step): void { | ||||
|     if (!component) { | ||||
|       throw new Error('component to add must not be empty or undefined'); | ||||
|     } | ||||
|     this.components.push(component); | ||||
|   } | ||||
| 
 | ||||
							
								
								
									
										33
									
								
								src/app/native-library-wrapper.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/app/native-library-wrapper.service.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| import {inject, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {NativeLibraryWrapperService} from './native-library-wrapper.service'; | ||||
| 
 | ||||
| describe('NativeLibraryWrapperService', () => { | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       providers: [NativeLibraryWrapperService] | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('should be created', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => { | ||||
|     expect(service).toBeTruthy(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should convert punycode', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => { | ||||
|     expect(service.punycode).toBeTruthy(); | ||||
|     expect(service.punycode.encode('bärneruhr')).toEqual('brneruhr-0za'); | ||||
|     expect(service.punycode.decode('brneruhr-0za')).toEqual('bärneruhr'); | ||||
|   })); | ||||
| 
 | ||||
|   it('should convert utf8', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => { | ||||
|     expect(service.utf8).toBeTruthy(); | ||||
|     expect(service.utf8.encode('bärneruhr')).toEqual('bärneruhr'); | ||||
|     expect(service.utf8.decode('bärneruhr')).toEqual('bärneruhr'); | ||||
|   })); | ||||
| 
 | ||||
|   it('should convert quoted printable', inject([NativeLibraryWrapperService], (service: NativeLibraryWrapperService) => { | ||||
|     expect(service.quotedPrintable).toBeTruthy(); | ||||
|     expect(service.quotedPrintable.encode('bärneruhr')).toEqual('b=E4rneruhr'); | ||||
|     expect(service.quotedPrintable.decode('b=E4rneruhr')).toEqual('bärneruhr'); | ||||
|   })); | ||||
| }); | ||||
							
								
								
									
										37
									
								
								src/app/native-library-wrapper.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/app/native-library-wrapper.service.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import {Injectable} from '@angular/core'; | ||||
| import * as NativePunycode from 'punycode/'; | ||||
| import * as NativeQuotedPrintable from 'quoted-printable'; | ||||
| import * as NativeUtf8 from 'utf8'; | ||||
| 
 | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class NativeLibraryWrapperService { | ||||
|   public readonly utf8: Utf8; | ||||
|   public readonly quotedPrintable: QuotedPrintable; | ||||
|   public readonly punycode: Punycode; | ||||
| 
 | ||||
|   constructor() { | ||||
|     this.utf8 = NativeUtf8; | ||||
|     this.quotedPrintable = NativeQuotedPrintable; | ||||
|     this.punycode = NativePunycode; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export interface Punycode { | ||||
|   encode(input: string): string; | ||||
| 
 | ||||
|   decode(input: string): string; | ||||
| } | ||||
| 
 | ||||
| export interface QuotedPrintable { | ||||
|   encode(input: string): string; | ||||
| 
 | ||||
|   decode(input: string): string; | ||||
| } | ||||
| 
 | ||||
| export interface Utf8 { | ||||
|   encode(input: any): string; | ||||
| 
 | ||||
|   decode(input: string): any; | ||||
| } | ||||
|  | @ -1,20 +0,0 @@ | |||
| import {Injectable} from '@angular/core'; | ||||
| import {Punycode} from './punycode'; | ||||
| import {Utf8} from './utf8'; | ||||
| import {QuotedPrintable} from './quotedprintable'; | ||||
| import * as NativeUtf8 from 'utf8'; | ||||
| import * as NativeQuotedPrintable from 'quoted-printable'; | ||||
| import * as NativePunycode from 'punycode'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class NativeLibraryWrapperService { | ||||
|   public utf8: Utf8; | ||||
|   public quotedPrintable: QuotedPrintable; | ||||
|   public punycode: Punycode; | ||||
| 
 | ||||
|   constructor() { | ||||
|     this.utf8 = NativeUtf8; | ||||
|     this.quotedPrintable = NativeQuotedPrintable; | ||||
|     this.punycode = NativePunycode; | ||||
|   } | ||||
| } | ||||
|  | @ -1,4 +0,0 @@ | |||
| export interface Punycode { | ||||
|   encode(input: string): string; | ||||
|   decode(input: string): string; | ||||
| } | ||||
|  | @ -1,4 +0,0 @@ | |||
| export interface QuotedPrintable { | ||||
|   encode(input: string): string; | ||||
|   decode(input: string): string; | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ import {Converter} from './converter/converter'; | |||
| 
 | ||||
| export class Step { | ||||
|   public content = ''; | ||||
|   public selectedConverter: Converter = undefined; | ||||
|   public selectedConverter: Converter | undefined; | ||||
|   public index: number; | ||||
|   public error = false; | ||||
|   public message = ''; | ||||
|  |  | |||
							
								
								
									
										4
									
								
								src/app/text-input-field/text-input-field.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/app/text-input-field/text-input-field.component.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| <div class="textwrapper arrow_box"> | ||||
|     <textarea class="textinput" (keyup)="update(step)" placeholder="Please enter your input ..." | ||||
|               [(ngModel)]="step.content">{{step.content}}</textarea> | ||||
| </div> | ||||
							
								
								
									
										69
									
								
								src/app/text-input-field/text-input-field.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/app/text-input-field/text-input-field.component.scss
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| .textwrapper { | ||||
|   margin: 0 0 1em 0; | ||||
|   padding: 0 1em 0 0; | ||||
| } | ||||
| 
 | ||||
| .arrow_box { | ||||
|   position: relative; | ||||
|   background: #fff; | ||||
|   border: 1px solid #aaa; | ||||
|   &:focus { | ||||
|     border-color: #888; | ||||
|   } | ||||
|   &:hover { | ||||
|     border-color: #333; | ||||
|   } | ||||
|   &:after, &:before { | ||||
|     top: 100%; | ||||
|     left: 50%; | ||||
|     border: solid transparent; | ||||
|     content: " "; | ||||
|     height: 0; | ||||
|     width: 0; | ||||
|     position: absolute; | ||||
|     pointer-events: none; | ||||
|   } | ||||
|   &:after { | ||||
|     border-color: rgba(255, 255, 255, 0); | ||||
|     border-top-color: #fff; | ||||
|     border-width: 1em; | ||||
|     margin-left: -1em; | ||||
|   } | ||||
|   &:before { | ||||
|     border-color: rgba(170, 170, 170, 0); | ||||
|     border-top-color: #aaa; | ||||
|     border-width: calc(1em + 1px); | ||||
|     margin-left: calc(-1em - 1px); | ||||
|   } | ||||
|   &:focus:before { | ||||
|     border-color: rgba(136, 136, 136, 0); | ||||
|     border-top-color: #888; | ||||
|   } | ||||
|   &:hover:before { | ||||
|     border-color: rgba(51, 51, 51, 0); | ||||
|     border-top-color: #333; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .textinput { | ||||
|   background-color: #fff; | ||||
|   border: none; | ||||
|   color: #000; | ||||
|   font-family: "Free Monospaced", monospace; | ||||
|   height: 10em; | ||||
|   margin: 0; | ||||
|   padding: 0.5em; | ||||
|   resize: vertical; | ||||
|   width: 100%; | ||||
|   &:focus { | ||||
|     border-color: #888; | ||||
|   } | ||||
|   &:hover { | ||||
|     border-color: #333; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .errormessage { | ||||
|   color: red; | ||||
|   text-align: center; | ||||
| } | ||||
							
								
								
									
										109
									
								
								src/app/text-input-field/text-input-field.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/app/text-input-field/text-input-field.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {TextInputFieldComponent} from './text-input-field.component'; | ||||
| import {Step} from '../step'; | ||||
| import {Component, ViewChild} from '@angular/core'; | ||||
| import {FormsModule} from '@angular/forms'; | ||||
| import {InputComponentManagerService} from '../input-component-manager.service'; | ||||
| import createSpyObj = jasmine.createSpyObj; | ||||
| 
 | ||||
| @Component({ | ||||
|   template: '<app-text-input-field [step]="step"></app-text-input-field>' | ||||
| }) | ||||
| class TestHostComponent { | ||||
|   public step: Step = new Step(42); | ||||
|   @ViewChild(TextInputFieldComponent) | ||||
|   public sutComponent!: TextInputFieldComponent; | ||||
| } | ||||
| 
 | ||||
| describe('TextInputFieldComponent', () => { | ||||
|   let testHostComponent: TestHostComponent; | ||||
|   let testHostFixture: ComponentFixture<TestHostComponent>; | ||||
| 
 | ||||
|   const inputComponentManagerServiceStub = createSpyObj(['getNext']); | ||||
|   inputComponentManagerServiceStub.getNext.and.returnValue(undefined); | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ | ||||
|         TestHostComponent | ||||
|       ], | ||||
|       imports: [TextInputFieldComponent, FormsModule], | ||||
|       providers: [{provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub}] | ||||
|     }) | ||||
|       .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     testHostFixture = TestBed.createComponent(TestHostComponent); | ||||
|     testHostComponent = testHostFixture.componentInstance; | ||||
|     testHostFixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create the component', () => { | ||||
|     expect(testHostComponent.sutComponent).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should not perform an update if no converter is selected', () => { | ||||
|     // arrange
 | ||||
|     const step: Step = testHostComponent.step; | ||||
|     step.content = 'Don\'t you change me!'; | ||||
|     step.error = false; | ||||
|     step.message = 'There be no message.'; | ||||
| 
 | ||||
|     // act
 | ||||
|     testHostComponent.sutComponent.update(step); | ||||
| 
 | ||||
|     // assert
 | ||||
|     expect(step.content).toEqual('Don\'t you change me!'); | ||||
|     expect(step.error).toBeFalsy(); | ||||
|     expect(step.message).toEqual('There be no message.'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should set the error flag if the conversion fails', () => { | ||||
|     // arrange
 | ||||
|     const step: Step = testHostComponent.step; | ||||
|     step.content = 'Don\'t you change me!'; | ||||
|     step.error = false; | ||||
|     step.message = 'There be no message.'; | ||||
|     step.selectedConverter = { | ||||
|       getId: () => 'testConverter', | ||||
|       convert: () => { | ||||
|         throw new Error('Test error'); | ||||
|       }, | ||||
|       getDisplayname: () => 'Testing converter' | ||||
|     }; | ||||
| 
 | ||||
|     // act
 | ||||
|     testHostComponent.sutComponent.update(step); | ||||
| 
 | ||||
|     // assert
 | ||||
|     expect(step.content).toEqual('Don\'t you change me!'); | ||||
|     expect(step.error).toBeTruthy(); | ||||
|     expect(step.message).toEqual('Test error'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should correctly convert valid input', () => { | ||||
|     // arrange
 | ||||
|     const nextStep: Step = new Step(82); | ||||
|     inputComponentManagerServiceStub.getNext.and.returnValue(nextStep); | ||||
|     const step: Step = testHostComponent.step; | ||||
|     step.content = 'Don\'t you change me!'; | ||||
|     step.error = true; | ||||
|     step.message = 'There be no message.'; | ||||
|     step.selectedConverter = { | ||||
|       getId: () => 'testConverter', | ||||
|       convert: () => 'The Converted Result', | ||||
|       getDisplayname: () => 'Testing converter' | ||||
|     }; | ||||
| 
 | ||||
|     // act
 | ||||
|     testHostComponent.sutComponent.update(step); | ||||
| 
 | ||||
|     // assert
 | ||||
|     expect(step.content).toEqual('Don\'t you change me!'); | ||||
|     expect(step.error).toBeFalsy(); | ||||
|     expect(step.message).toEqual(''); | ||||
|     expect(nextStep.content).toEqual('The Converted Result'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										48
									
								
								src/app/text-input-field/text-input-field.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/app/text-input-field/text-input-field.component.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| import {Component, Input} from '@angular/core'; | ||||
| import {Step} from '../step'; | ||||
| import {Converter} from '../converter/converter'; | ||||
| import {InputComponentManagerService} from '../input-component-manager.service'; | ||||
| import {FormsModule} from '@angular/forms'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-text-input-field', | ||||
|   templateUrl: './text-input-field.component.html', | ||||
|   standalone: true, | ||||
|   styleUrls: ['./text-input-field.component.scss'], | ||||
|   imports: [FormsModule] | ||||
| }) | ||||
| export class TextInputFieldComponent { | ||||
|   @Input() | ||||
|   public step!: Step; | ||||
| 
 | ||||
|   constructor(private inputComponentManagerService: InputComponentManagerService) { | ||||
|   } | ||||
| 
 | ||||
|   update(step: Step): void { | ||||
|     const converter: Converter | undefined = step.selectedConverter; | ||||
| 
 | ||||
|     if (converter !== undefined) { | ||||
|       const content: string = step.content; | ||||
|       let result: string | null; | ||||
|       try { | ||||
|         result = converter.convert(content); | ||||
|       } catch (error) { | ||||
|         if (typeof console === 'object' && typeof console.log === 'function') { | ||||
|           console.log(error); | ||||
|         } | ||||
|         step.message = (error as Error).message; | ||||
|         step.error = true; | ||||
|         result = null; | ||||
|       } | ||||
|       if (result !== null) { | ||||
|         step.message = ''; | ||||
|         step.error = false; | ||||
|         if (result !== '') { | ||||
|           const nextComponent: Step = this.inputComponentManagerService.getNext(step); | ||||
|           nextComponent.content = result; | ||||
|           this.update(nextComponent); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,4 +0,0 @@ | |||
| export interface Utf8 { | ||||
|   encode(input: any): string; | ||||
|   decode(input: string): any; | ||||
| } | ||||
							
								
								
									
										1
									
								
								src/app/version/version.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/app/version/version.component.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| <div [ngClass]="{dev: !PROD}">Version: {{VERSION}}</div> | ||||
							
								
								
									
										10
									
								
								src/app/version/version.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/app/version/version.component.scss
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| div { | ||||
|   font-size: smaller; | ||||
|   color: gray; | ||||
|   display: block; | ||||
|   text-align: right; | ||||
|   margin-right: 2em; | ||||
|   &.dev { | ||||
|     color: red; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/app/version/version.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/app/version/version.component.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||||
| 
 | ||||
| import {VersionComponent} from './version.component'; | ||||
| import packageInfo from '../../../package.json'; | ||||
| 
 | ||||
| describe('VersionComponent', () => { | ||||
|   let component: VersionComponent; | ||||
|   let fixture: ComponentFixture<VersionComponent>; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       imports: [VersionComponent] | ||||
|     }) | ||||
|       .compileComponents(); | ||||
|   }); | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     fixture = TestBed.createComponent(VersionComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should create the component', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should contain the correct version', () => { | ||||
|     // when executing the test, we're always running with the dev environment
 | ||||
|     expect(component.VERSION).toEqual(`${packageInfo.version} (development build)`); | ||||
|   }); | ||||
| 
 | ||||
|   it('should have the correct value for the "production" property', () => { | ||||
|     // when executing the test, we're always running with the dev environment
 | ||||
|     expect(component.PROD).toBeFalsy(); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										18
									
								
								src/app/version/version.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/app/version/version.component.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import {Component, isDevMode} from '@angular/core'; | ||||
| import {CommonModule} from '@angular/common'; | ||||
| import packageInfo from '../../../package.json'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-version', | ||||
|   templateUrl: './version.component.html', | ||||
|   standalone: true, | ||||
|   styleUrls: ['./version.component.scss'], | ||||
|   imports: [CommonModule] | ||||
| }) | ||||
| export class VersionComponent { | ||||
|   public readonly PROD: boolean = !isDevMode(); | ||||
|   public readonly VERSION: string = packageInfo.version + (this.PROD ? '' : ' (development build)'); | ||||
| 
 | ||||
|   constructor() { | ||||
|   } | ||||
| } | ||||
|  | @ -1,11 +0,0 @@ | |||
| # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers | ||||
| # For additional information regarding the format and rule options, please see: | ||||
| # https://github.com/browserslist/browserslist#queries | ||||
| # | ||||
| # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed | ||||
| 
 | ||||
| > 0.5% | ||||
| last 2 versions | ||||
| Firefox ESR | ||||
| not dead | ||||
| not IE 9-11 | ||||
|  | @ -1,3 +0,0 @@ | |||
| export const environment = { | ||||
|   production: true | ||||
| }; | ||||
|  | @ -1,15 +0,0 @@ | |||
| // This file can be replaced during build by using the `fileReplacements` array.
 | ||||
| // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
 | ||||
| // The list of file replacements can be found in `angular.json`.
 | ||||
| 
 | ||||
| export const environment = { | ||||
|   production: false | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  * In development mode, for easier debugging, you can ignore zone related error | ||||
|  * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the | ||||
|  * below file. Don't forget to comment it out in production mode | ||||
|  * because it will have a performance impact when errors are thrown | ||||
|  */ | ||||
| // import 'zone.js/dist/zone-error';  // Included with Angular CLI.
 | ||||
|  | @ -1,31 +0,0 @@ | |||
| // Karma configuration file, see link for more information
 | ||||
| // https://karma-runner.github.io/1.0/config/configuration-file.html
 | ||||
| 
 | ||||
| module.exports = function (config) { | ||||
|   config.set({ | ||||
|     basePath: '', | ||||
|     frameworks: ['jasmine', '@angular-devkit/build-angular'], | ||||
|     plugins: [ | ||||
|       require('karma-jasmine'), | ||||
|       require('karma-chrome-launcher'), | ||||
|       require('karma-jasmine-html-reporter'), | ||||
|       require('karma-coverage-istanbul-reporter'), | ||||
|       require('@angular-devkit/build-angular/plugins/karma') | ||||
|     ], | ||||
|     client: { | ||||
|       clearContext: false // leave Jasmine Spec Runner output visible in browser
 | ||||
|     }, | ||||
|     coverageIstanbulReporter: { | ||||
|       dir: require('path').join(__dirname, '../coverage'), | ||||
|       reports: ['html', 'lcovonly'], | ||||
|       fixWebpackSourcePaths: true | ||||
|     }, | ||||
|     reporters: ['progress', 'kjhtml'], | ||||
|     port: 9876, | ||||
|     colors: true, | ||||
|     logLevel: config.LOG_INFO, | ||||
|     autoWatch: true, | ||||
|     browsers: ['Chrome'], | ||||
|     singleRun: false | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										16
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								src/main.ts
									
										
									
									
									
								
							|  | @ -1,12 +1,6 @@ | |||
| import { enableProdMode } from '@angular/core'; | ||||
| import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; | ||||
| import { bootstrapApplication } from '@angular/platform-browser'; | ||||
| import { appConfig } from './app/app.config'; | ||||
| import { AppComponent } from './app/app.component'; | ||||
| 
 | ||||
| import { AppModule } from './app/app.module'; | ||||
| import { environment } from './environments/environment'; | ||||
| 
 | ||||
| if (environment.production) { | ||||
|   enableProdMode(); | ||||
| } | ||||
| 
 | ||||
| platformBrowserDynamic().bootstrapModule(AppModule) | ||||
|   .catch(err => console.log(err)); | ||||
| bootstrapApplication(AppComponent, appConfig) | ||||
|   .catch((err) => console.error(err)); | ||||
|  |  | |||
|  | @ -1,76 +0,0 @@ | |||
| /** | ||||
|  * This file includes polyfills needed by Angular and is loaded before the app. | ||||
|  * You can add your own extra polyfills to this file. | ||||
|  * | ||||
|  * This file is divided into 2 sections: | ||||
|  *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. | ||||
|  *   2. Application imports. Files imported after ZoneJS that should be loaded before your main | ||||
|  *      file. | ||||
|  * | ||||
|  * The current setup is for so-called "evergreen" browsers; the last versions of browsers that | ||||
|  * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), | ||||
|  * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. | ||||
|  * | ||||
|  * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
 | ||||
|  */ | ||||
| 
 | ||||
| /*************************************************************************************************** | ||||
|  * BROWSER POLYFILLS | ||||
|  */ | ||||
| 
 | ||||
| /** IE9, IE10 and IE11 requires all of the following polyfills. **/ | ||||
| // import 'core-js/es6/symbol';
 | ||||
| // import 'core-js/es6/object';
 | ||||
| // import 'core-js/es6/function';
 | ||||
| // import 'core-js/es6/parse-int';
 | ||||
| // import 'core-js/es6/parse-float';
 | ||||
| // import 'core-js/es6/number';
 | ||||
| // import 'core-js/es6/math';
 | ||||
| // import 'core-js/es6/string';
 | ||||
| // import 'core-js/es6/date';
 | ||||
| // import 'core-js/es6/array';
 | ||||
| // import 'core-js/es6/regexp';
 | ||||
| // import 'core-js/es6/map';
 | ||||
| // import 'core-js/es6/weak-map';
 | ||||
| // import 'core-js/es6/set';
 | ||||
| 
 | ||||
| /** IE10 and IE11 requires the following for NgClass support on SVG elements */ | ||||
| // import 'classlist.js';  // Run `npm install --save classlist.js`.
 | ||||
| 
 | ||||
| /** IE10 and IE11 requires the following for the Reflect API. */ | ||||
| import 'core-js/es6/reflect'; | ||||
| /** Evergreen browsers require these. **/ | ||||
| // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
 | ||||
| import 'core-js/es7/reflect'; | ||||
| /*************************************************************************************************** | ||||
|  * Zone JS is required by default for Angular itself. | ||||
|  */ | ||||
| import 'zone.js/dist/zone'; // Included with Angular CLI.
 | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Web Animations `@angular/platform-browser/animations` | ||||
|  * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. | ||||
|  * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). | ||||
|  **/ | ||||
| // import 'web-animations-js';  // Run `npm install --save web-animations-js`.
 | ||||
| 
 | ||||
| /** | ||||
|  * By default, zone.js will patch all possible macroTask and DomEvents | ||||
|  * user can disable parts of macroTask/DomEvents patch by setting following flags | ||||
|  */ | ||||
| 
 | ||||
| // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 | ||||
| // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 | ||||
| // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 | ||||
| 
 | ||||
| /* | ||||
| * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js | ||||
| * with the following flag, it will bypass `zone.js` patch for IE/Edge | ||||
| */ | ||||
| // (window as any).__Zone_enable_cross_context_check = true;
 | ||||
| 
 | ||||
| 
 | ||||
| /*************************************************************************************************** | ||||
|  * APPLICATION IMPORTS | ||||
|  */ | ||||
							
								
								
									
										20
									
								
								src/test.ts
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								src/test.ts
									
										
									
									
									
								
							|  | @ -1,20 +0,0 @@ | |||
| // This file is required by karma.conf.js and loads recursively all the .spec and framework files
 | ||||
| 
 | ||||
| import 'zone.js/dist/zone-testing'; | ||||
| import { getTestBed } from '@angular/core/testing'; | ||||
| import { | ||||
|   BrowserDynamicTestingModule, | ||||
|   platformBrowserDynamicTesting | ||||
| } from '@angular/platform-browser-dynamic/testing'; | ||||
| 
 | ||||
| declare const require: any; | ||||
| 
 | ||||
| // First, initialize the Angular testing environment.
 | ||||
| getTestBed().initTestEnvironment( | ||||
|   BrowserDynamicTestingModule, | ||||
|   platformBrowserDynamicTesting() | ||||
| ); | ||||
| // Then we find all the tests.
 | ||||
| const context = require.context('./', true, /\.spec\.ts$/); | ||||
| // And load the modules.
 | ||||
| context.keys().map(context); | ||||
|  | @ -1,11 +0,0 @@ | |||
| { | ||||
|   "extends": "../tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "../out-tsc/app", | ||||
|     "types": [] | ||||
|   }, | ||||
|   "exclude": [ | ||||
|     "test.ts", | ||||
|     "**/*.spec.ts" | ||||
|   ] | ||||
| } | ||||
|  | @ -1,18 +0,0 @@ | |||
| { | ||||
|   "extends": "../tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "../out-tsc/spec", | ||||
|     "types": [ | ||||
|       "jasmine", | ||||
|       "node" | ||||
|     ] | ||||
|   }, | ||||
|   "files": [ | ||||
|     "test.ts", | ||||
|     "polyfills.ts" | ||||
|   ], | ||||
|   "include": [ | ||||
|     "**/*.spec.ts", | ||||
|     "**/*.d.ts" | ||||
|   ] | ||||
| } | ||||
|  | @ -1,17 +0,0 @@ | |||
| { | ||||
|     "extends": "../tslint.json", | ||||
|     "rules": { | ||||
|         "directive-selector": [ | ||||
|             true, | ||||
|             "attribute", | ||||
|             "app", | ||||
|             "camelCase" | ||||
|         ], | ||||
|         "component-selector": [ | ||||
|             true, | ||||
|             "element", | ||||
|             "app", | ||||
|             "kebab-case" | ||||
|         ] | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								tsconfig.app.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tsconfig.app.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| /* To learn more about this file see: https://angular.io/config/tsconfig. */ | ||||
| { | ||||
|   "extends": "./tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "./out-tsc/app", | ||||
|     "types": [ | ||||
|       "node" | ||||
|     ] | ||||
|   }, | ||||
|   "files": [ | ||||
|     "src/main.ts" | ||||
|   ], | ||||
|   "include": [ | ||||
|     "src/**/*.d.ts" | ||||
|   ] | ||||
| } | ||||
|  | @ -1,21 +1,35 @@ | |||
| /* To learn more about this file see: https://angular.io/config/tsconfig. */ | ||||
| { | ||||
|   "compileOnSave": false, | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": "./", | ||||
|     "outDir": "./dist/out-tsc", | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "strict": true, | ||||
|     "noImplicitOverride": true, | ||||
|     "noPropertyAccessFromIndexSignature": true, | ||||
|     "noImplicitReturns": true, | ||||
|     "noFallthroughCasesInSwitch": true, | ||||
|     "skipLibCheck": true, | ||||
|     "esModuleInterop": true, | ||||
|     "sourceMap": true, | ||||
|     "declaration": false, | ||||
|     "module": "es2015", | ||||
|     "moduleResolution": "node", | ||||
|     "emitDecoratorMetadata": true, | ||||
|     "experimentalDecorators": true, | ||||
|     "target": "es5", | ||||
|     "typeRoots": [ | ||||
|       "node_modules/@types" | ||||
|     ], | ||||
|     "moduleResolution": "node", | ||||
|     "importHelpers": true, | ||||
|     "target": "ES2022", | ||||
|     "module": "ES2022", | ||||
|     "useDefineForClassFields": false, | ||||
|     "lib": [ | ||||
|       "es2017", | ||||
|       "ES2022", | ||||
|       "dom" | ||||
|     ] | ||||
|     ], | ||||
|     "resolveJsonModule": true, | ||||
|     "allowSyntheticDefaultImports": true | ||||
|   }, | ||||
|   "angularCompilerOptions": { | ||||
|     "enableI18nLegacyMessageIdFormat": false, | ||||
|     "strictInjectionParameters": true, | ||||
|     "strictInputAccessModifiers": true, | ||||
|     "strictTemplates": true | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										14
									
								
								tsconfig.spec.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tsconfig.spec.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| /* To learn more about this file see: https://angular.io/config/tsconfig. */ | ||||
| { | ||||
|   "extends": "./tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "./out-tsc/spec", | ||||
|     "types": [ | ||||
|       "jasmine" | ||||
|     ] | ||||
|   }, | ||||
|   "include": [ | ||||
|     "src/**/*.spec.ts", | ||||
|     "src/**/*.d.ts" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										130
									
								
								tslint.json
									
										
									
									
									
								
							
							
						
						
									
										130
									
								
								tslint.json
									
										
									
									
									
								
							|  | @ -1,130 +0,0 @@ | |||
| { | ||||
|   "rulesDirectory": [ | ||||
|     "node_modules/codelyzer" | ||||
|   ], | ||||
|   "rules": { | ||||
|     "arrow-return-shorthand": true, | ||||
|     "callable-types": true, | ||||
|     "class-name": true, | ||||
|     "comment-format": [ | ||||
|       true, | ||||
|       "check-space" | ||||
|     ], | ||||
|     "curly": true, | ||||
|     "deprecation": { | ||||
|       "severity": "warn" | ||||
|     }, | ||||
|     "eofline": true, | ||||
|     "forin": true, | ||||
|     "import-blacklist": [ | ||||
|       true, | ||||
|       "rxjs/Rx" | ||||
|     ], | ||||
|     "import-spacing": true, | ||||
|     "indent": [ | ||||
|       true, | ||||
|       "spaces" | ||||
|     ], | ||||
|     "interface-over-type-literal": true, | ||||
|     "label-position": true, | ||||
|     "max-line-length": [ | ||||
|       true, | ||||
|       140 | ||||
|     ], | ||||
|     "member-access": false, | ||||
|     "member-ordering": [ | ||||
|       true, | ||||
|       { | ||||
|         "order": [ | ||||
|           "static-field", | ||||
|           "instance-field", | ||||
|           "static-method", | ||||
|           "instance-method" | ||||
|         ] | ||||
|       } | ||||
|     ], | ||||
|     "no-arg": true, | ||||
|     "no-bitwise": true, | ||||
|     "no-console": [ | ||||
|       true, | ||||
|       "debug", | ||||
|       "info", | ||||
|       "time", | ||||
|       "timeEnd", | ||||
|       "trace" | ||||
|     ], | ||||
|     "no-construct": true, | ||||
|     "no-debugger": true, | ||||
|     "no-duplicate-super": true, | ||||
|     "no-empty": false, | ||||
|     "no-empty-interface": true, | ||||
|     "no-eval": true, | ||||
|     "no-inferrable-types": [ | ||||
|       true, | ||||
|       "ignore-params" | ||||
|     ], | ||||
|     "no-misused-new": true, | ||||
|     "no-non-null-assertion": true, | ||||
|     "no-shadowed-variable": true, | ||||
|     "no-string-literal": false, | ||||
|     "no-string-throw": true, | ||||
|     "no-switch-case-fall-through": true, | ||||
|     "no-trailing-whitespace": true, | ||||
|     "no-unnecessary-initializer": true, | ||||
|     "no-unused-expression": true, | ||||
|     "no-use-before-declare": true, | ||||
|     "no-var-keyword": true, | ||||
|     "object-literal-sort-keys": false, | ||||
|     "one-line": [ | ||||
|       true, | ||||
|       "check-open-brace", | ||||
|       "check-catch", | ||||
|       "check-else", | ||||
|       "check-whitespace" | ||||
|     ], | ||||
|     "prefer-const": true, | ||||
|     "quotemark": [ | ||||
|       true, | ||||
|       "single" | ||||
|     ], | ||||
|     "radix": true, | ||||
|     "semicolon": [ | ||||
|       true, | ||||
|       "always" | ||||
|     ], | ||||
|     "triple-equals": [ | ||||
|       true, | ||||
|       "allow-null-check" | ||||
|     ], | ||||
|     "typedef-whitespace": [ | ||||
|       true, | ||||
|       { | ||||
|         "call-signature": "nospace", | ||||
|         "index-signature": "nospace", | ||||
|         "parameter": "nospace", | ||||
|         "property-declaration": "nospace", | ||||
|         "variable-declaration": "nospace" | ||||
|       } | ||||
|     ], | ||||
|     "unified-signatures": true, | ||||
|     "variable-name": false, | ||||
|     "whitespace": [ | ||||
|       true, | ||||
|       "check-branch", | ||||
|       "check-decl", | ||||
|       "check-operator", | ||||
|       "check-separator", | ||||
|       "check-type" | ||||
|     ], | ||||
|     "no-output-on-prefix": true, | ||||
|     "use-input-property-decorator": true, | ||||
|     "use-output-property-decorator": true, | ||||
|     "use-host-property-decorator": true, | ||||
|     "no-input-rename": true, | ||||
|     "no-output-rename": true, | ||||
|     "use-life-cycle-interface": true, | ||||
|     "use-pipe-transform-interface": true, | ||||
|     "component-class-suffix": true, | ||||
|     "directive-class-suffix": true | ||||
|   } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue