diff --git a/README.md b/README.md
index e0b8242..f2439bf 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
This is a short introduction to the awesome Convertorizr hosted at https://conv.friedli.info/.
-Continuous integration is automated with Drone CI (![Build status of develop branch](https://ci.gittr.ch/api/badges/manuel/converter/status.svg?branch=develop)). Usage is self-explanatory. What else do you need to know?
+Continuous integration is automated with Drone CI ([![Build status of develop branch](https://ci.gittr.ch/api/badges/manuel/converter/status.svg?branch=develop)](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.
diff --git a/package.json b/package.json
index 3c55b59..a095b9a 100644
--- a/package.json
+++ b/package.json
@@ -24,8 +24,8 @@
"start": "ng serve",
"build": "ng build --delete-output-path",
"build-prod": "ng build --prod --optimization --aot --delete-output-path --build-optimizer",
- "test": "ng test",
- "test:ci": "ng test --browsers ChromeHeadlessNoSandbox --watch=false",
+ "test": "ng test --code-coverage",
+ "test:ci": "ng test --browsers ChromeHeadlessNoSandbox --watch=false --code-coverage",
"lint": "ng lint",
"e2e": "ng e2e",
"postinstall": "npm rebuild node-sass"
diff --git a/src/app/app.component.html b/src/app/app.component.html
index c96ed95..35fc20f 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,18 +1,7 @@
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 9a81a0c..63be152 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -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;
-}
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
index 36d8222..1a66e4e 100644
--- a/src/app/app.component.spec.ts
+++ b/src/app/app.component.spec.ts
@@ -1,32 +1,27 @@
import {AppComponent} from './app.component';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
-import {InputComponentManagerService} from './input-component-manager.service';
import {Step} from './step';
-import {ConverterRegistryService} from './converter-registry.service';
import {VersionComponent} from './version/version.component';
-import createSpyObj = jasmine.createSpyObj;
+import {ConverterSelectorComponent} from './converter-selector/converter-selector.component';
+import {TextInputFieldComponent} from './text-input-field/text-input-field.component';
+import {ErrorMessageComponent} from './error-message/error-message.component';
describe('AppComponent', () => {
let sut: AppComponent;
let fixture: ComponentFixture;
const firstStep: Step = new Step(0);
- const inputComponentManagerServiceStub = createSpyObj(['getFirst']);
- inputComponentManagerServiceStub.getFirst.and.returnValue(firstStep);
-
- const converterRegistryServiceStub = createSpyObj(['getAllConverters', 'getConverter']);
- converterRegistryServiceStub.getAllConverters.and.returnValue([]);
- converterRegistryServiceStub.getConverter.and.returnValue(undefined);
-
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [AppComponent, VersionComponent],
- imports: [FormsModule],
- providers: [
- {provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub},
- {provide: ConverterRegistryService, useValue: converterRegistryServiceStub}
- ]
+ declarations: [
+ AppComponent,
+ ConverterSelectorComponent,
+ ErrorMessageComponent,
+ TextInputFieldComponent,
+ VersionComponent
+ ],
+ imports: [FormsModule]
})
.compileComponents();
}));
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 820811b..051970e 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,8 +1,6 @@
import {Component, OnInit} from '@angular/core';
-import {ConverterRegistryService} from './converter-registry.service';
import {InputComponentManagerService} from './input-component-manager.service';
import {Step} from './step';
-import {Converter} from './converter/converter';
@Component({
selector: 'app-root',
@@ -11,47 +9,11 @@ import {Converter} from './converter/converter';
})
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();
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 5e40ab8..875f69f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -6,11 +6,17 @@ import {InputComponentManagerService} from './input-component-manager.service';
import {NativeLibraryWrapperService} from './native-library-wrapper.service';
import {FormsModule} from '@angular/forms';
import {VersionComponent} from './version/version.component';
+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';
@NgModule({
declarations: [
AppComponent,
- VersionComponent
+ VersionComponent,
+ TextInputFieldComponent,
+ ConverterSelectorComponent,
+ ErrorMessageComponent
],
imports: [
BrowserModule,
diff --git a/src/app/converter-registry.service.ts b/src/app/converter-registry.service.ts
index d84733b..85e6304 100644
--- a/src/app/converter-registry.service.ts
+++ b/src/app/converter-registry.service.ts
@@ -36,12 +36,7 @@ export class ConverterRegistryService {
}
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;
+ return this.converters.find((converter: Converter): boolean => converter.getId() === id);
}
private init(): void {
@@ -67,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);
}
}
diff --git a/src/app/converter-selector/converter-selector.component.html b/src/app/converter-selector/converter-selector.component.html
new file mode 100644
index 0000000..5c3364c
--- /dev/null
+++ b/src/app/converter-selector/converter-selector.component.html
@@ -0,0 +1,9 @@
+
+
+
+ Select conversion ...
+ {{c.getDisplayname()}}
+
+
+
+
diff --git a/src/app/converter-selector/converter-selector.component.scss b/src/app/converter-selector/converter-selector.component.scss
new file mode 100644
index 0000000..b31f217
--- /dev/null
+++ b/src/app/converter-selector/converter-selector.component.scss
@@ -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;*/
+}
diff --git a/src/app/converter-selector/converter-selector.component.spec.ts b/src/app/converter-selector/converter-selector.component.spec.ts
new file mode 100644
index 0000000..e37fdd0
--- /dev/null
+++ b/src/app/converter-selector/converter-selector.component.spec.ts
@@ -0,0 +1,93 @@
+import {async, 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: ' '
+})
+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;
+
+ const converter1: SpyObj = createSpyObj(['getId', 'getDisplayname', 'convert']);
+ const converter2: SpyObj = createSpyObj(['getId', 'getDisplayname', 'convert']);
+ const converter3: SpyObj = 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(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ ConverterSelectorComponent,
+ TestHostComponent
+ ],
+ providers: [
+ {provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub},
+ {provide: ConverterRegistryService, useValue: converterRegistryServiceStub}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+ });
+
+ it('should create', () => {
+ 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);
+ });
+});
diff --git a/src/app/converter-selector/converter-selector.component.ts b/src/app/converter-selector/converter-selector.component.ts
new file mode 100644
index 0000000..5aeba29
--- /dev/null
+++ b/src/app/converter-selector/converter-selector.component.ts
@@ -0,0 +1,30 @@
+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';
+
+@Component({
+ selector: 'app-converter-selector',
+ templateUrl: './converter-selector.component.html',
+ styleUrls: ['./converter-selector.component.scss']
+})
+export class ConverterSelectorComponent implements OnInit {
+ @Input()
+ public step: Step;
+ @Input()
+ private 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();
+ }
+}
diff --git a/src/app/error-message/error-message.component.html b/src/app/error-message/error-message.component.html
new file mode 100644
index 0000000..6c306bd
--- /dev/null
+++ b/src/app/error-message/error-message.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/error-message/error-message.component.scss b/src/app/error-message/error-message.component.scss
new file mode 100644
index 0000000..ef9b713
--- /dev/null
+++ b/src/app/error-message/error-message.component.scss
@@ -0,0 +1,4 @@
+.errormessage {
+ color: red;
+ text-align: center;
+}
diff --git a/src/app/error-message/error-message.component.spec.ts b/src/app/error-message/error-message.component.spec.ts
new file mode 100644
index 0000000..7b43709
--- /dev/null
+++ b/src/app/error-message/error-message.component.spec.ts
@@ -0,0 +1,52 @@
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {ErrorMessageComponent} from './error-message.component';
+import {Component, ViewChild} from '@angular/core';
+import {Step} from '../step';
+
+@Component({
+ template: ' '
+})
+class TestHostComponent {
+ public step: Step = new Step(42);
+ @ViewChild(ErrorMessageComponent)
+ public sutComponent: ErrorMessageComponent;
+}
+
+describe('ErrorMessageComponent', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ ErrorMessageComponent,
+ 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');
+ });
+});
diff --git a/src/app/error-message/error-message.component.ts b/src/app/error-message/error-message.component.ts
new file mode 100644
index 0000000..8db15df
--- /dev/null
+++ b/src/app/error-message/error-message.component.ts
@@ -0,0 +1,15 @@
+import {Component, Input} from '@angular/core';
+import {Step} from '../step';
+
+@Component({
+ selector: 'app-error-message',
+ templateUrl: './error-message.component.html',
+ styleUrls: ['./error-message.component.scss']
+})
+export class ErrorMessageComponent {
+ @Input()
+ public step: Step;
+
+ constructor() {
+ }
+}
diff --git a/src/app/text-input-field/text-input-field.component.html b/src/app/text-input-field/text-input-field.component.html
new file mode 100644
index 0000000..7000fb7
--- /dev/null
+++ b/src/app/text-input-field/text-input-field.component.html
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/app/text-input-field/text-input-field.component.scss b/src/app/text-input-field/text-input-field.component.scss
new file mode 100644
index 0000000..89dc5f9
--- /dev/null
+++ b/src/app/text-input-field/text-input-field.component.scss
@@ -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;
+}
diff --git a/src/app/text-input-field/text-input-field.component.spec.ts b/src/app/text-input-field/text-input-field.component.spec.ts
new file mode 100644
index 0000000..d71f04a
--- /dev/null
+++ b/src/app/text-input-field/text-input-field.component.spec.ts
@@ -0,0 +1,110 @@
+import {async, 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: ' '
+})
+class TestHostComponent {
+ public step: Step = new Step(42);
+ @ViewChild(TextInputFieldComponent)
+ public sutComponent: TextInputFieldComponent;
+}
+
+describe('TextInputFieldComponent', () => {
+ let testHostComponent: TestHostComponent;
+ let testHostFixture: ComponentFixture;
+
+ const inputComponentManagerServiceStub = createSpyObj(['getNext']);
+ inputComponentManagerServiceStub.getNext.and.returnValue(undefined);
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [
+ TestHostComponent,
+ TextInputFieldComponent
+ ],
+ imports: [FormsModule],
+ providers: [{provide: InputComponentManagerService, useValue: inputComponentManagerServiceStub}]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ testHostFixture = TestBed.createComponent(TestHostComponent);
+ testHostComponent = testHostFixture.componentInstance;
+ testHostFixture.detectChanges();
+ });
+
+ it('should create', () => {
+ 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');
+ });
+});
diff --git a/src/app/text-input-field/text-input-field.component.ts b/src/app/text-input-field/text-input-field.component.ts
new file mode 100644
index 0000000..932db29
--- /dev/null
+++ b/src/app/text-input-field/text-input-field.component.ts
@@ -0,0 +1,45 @@
+import {Component, Input} from '@angular/core';
+import {Step} from '../step';
+import {Converter} from '../converter/converter';
+import {InputComponentManagerService} from '../input-component-manager.service';
+
+@Component({
+ selector: 'app-text-input-field',
+ templateUrl: './text-input-field.component.html',
+ styleUrls: ['./text-input-field.component.scss']
+})
+export class TextInputFieldComponent {
+ @Input()
+ public step: Step;
+
+ constructor(private inputComponentManagerService: InputComponentManagerService) {
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+}