Skip to content

Commit ccb947d

Browse files
author
Jan Jelschen
committed
migrate and adapt indicator box component from workspace navigator
1 parent 4f2a293 commit ccb947d

9 files changed

+257
-1
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ testem.log
4141
/e2e/*.js
4242
/e2e/*.map
4343

44+
# app
45+
/src/**/*.js
46+
/src/**/*.map
47+
4448
# System Files
4549
.DS_Store
4650
Thumbs.db

src/app/modules/widgets/tree-viewer/indicator-box/indicator-box.component.css

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div [title]="label" [class]="cssClasses"></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { Component, ViewChild } from '@angular/core';
2+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { MarkerState } from '../markers/marker.state';
4+
import { TreeNode } from '../tree-node';
5+
import { IndicatorBoxComponent } from './indicator-box.component';
6+
import { By } from '@angular/platform-browser';
7+
8+
describe('IndicatorBoxComponent', () => {
9+
let component: IndicatorBoxComponent;
10+
let hostComponent: TestHostComponent;
11+
let fixture: ComponentFixture<TestHostComponent>;
12+
13+
const sampleMarkerStates: MarkerState[] = [{
14+
condition: (marker) => marker.sampleField && marker.otherField === 'test',
15+
cssClasses: 'someClass',
16+
label: (marker) => `sampleField = ${marker.sampleField}`,
17+
}, {
18+
condition: (marker) => !marker.sampleField,
19+
cssClasses: 'otherClass',
20+
label: (marker) => `otherField = ${marker.otherField}`,
21+
}];
22+
23+
@Component({
24+
selector: `app-host-component`,
25+
template: `<app-indicator-box [model]="{'node': node, 'possibleStates': states}"></app-indicator-box>`
26+
})
27+
class TestHostComponent {
28+
@ViewChild(IndicatorBoxComponent)
29+
public indicatorBoxComponentUnderTest: IndicatorBoxComponent;
30+
31+
node: TreeNode;
32+
states: MarkerState[];
33+
}
34+
35+
beforeEach(async(() => {
36+
TestBed.configureTestingModule({
37+
declarations: [
38+
IndicatorBoxComponent, TestHostComponent
39+
]
40+
})
41+
.compileComponents();
42+
}));
43+
44+
beforeEach(() => {
45+
fixture = TestBed.createComponent(TestHostComponent);
46+
hostComponent = fixture.componentInstance;
47+
component = hostComponent.indicatorBoxComponentUnderTest;
48+
});
49+
50+
it('Can be instantiated', () => {
51+
expect(component).toBeTruthy();
52+
});
53+
54+
it('sets the css classes of the active marker state and "fa-fw"', () => {
55+
// given
56+
const marker = {sampleField: true, otherField: 'test'};
57+
hostComponent.node = {children: [], name: 'sampleNode', root: null, marker: marker };
58+
hostComponent.states = sampleMarkerStates;
59+
fixture.detectChanges();
60+
61+
// when
62+
const actualCssClasses = hostComponent.indicatorBoxComponentUnderTest.cssClasses;
63+
64+
// then
65+
expect(actualCssClasses).toEqual('someClass fa-fw');
66+
});
67+
68+
it('uses the active marker state`s label and css classes', () => {
69+
// given
70+
const marker = {sampleField: true, otherField: 'test'};
71+
hostComponent.node = {children: [], name: 'sampleNode', root: null, marker: marker };
72+
hostComponent.states = sampleMarkerStates;
73+
74+
// when
75+
fixture.detectChanges();
76+
77+
// then
78+
const indicatorBoxTag = fixture.debugElement.query(By.css('div'));
79+
expect(indicatorBoxTag.nativeElement.attributes['title'].value).toEqual('sampleField = true');
80+
expect(indicatorBoxTag.nativeElement.className).toEqual('someClass fa-fw');
81+
});
82+
83+
it('changes label and css classes in accordance with changing marker states', () => {
84+
// given
85+
const marker = {sampleField: true, otherField: 'test'};
86+
hostComponent.node = {children: [], name: 'sampleNode', root: null, marker: marker };
87+
hostComponent.states = sampleMarkerStates;
88+
fixture.detectChanges();
89+
const indicatorBoxTag = fixture.debugElement.query(By.css('div'));
90+
expect(indicatorBoxTag.nativeElement.attributes['title'].value).toEqual('sampleField = true');
91+
expect(indicatorBoxTag.nativeElement.className).toEqual('someClass fa-fw');
92+
93+
// when
94+
marker.sampleField = false;
95+
fixture.detectChanges();
96+
97+
// then
98+
expect(indicatorBoxTag.nativeElement.attributes['title'].value).toEqual('otherField = test');
99+
expect(indicatorBoxTag.nativeElement.className).toEqual('otherClass fa-fw');
100+
});
101+
102+
it('handles exceptions in condition expressions gracefully and allows other state to become active', () => {
103+
// given
104+
const marker = {sampleField: true, otherField: 'test'};
105+
hostComponent.node = {children: [], name: 'sampleNode', root: null, marker: marker };
106+
hostComponent.states = sampleMarkerStates.slice(0);
107+
hostComponent.states.unshift({
108+
condition: (marker_) => marker_.nonExisting.property === true,
109+
cssClasses: 'brokenStateClass',
110+
label: (marker_) => { throw new Error('broken label provider'); },
111+
});
112+
113+
// when
114+
fixture.detectChanges();
115+
116+
// then
117+
const indicatorBoxTag = fixture.debugElement.query(By.css('div'));
118+
expect(indicatorBoxTag.nativeElement.attributes['title'].value).toEqual('sampleField = true');
119+
expect(indicatorBoxTag.nativeElement.className).toEqual('someClass fa-fw');
120+
});
121+
122+
it('handles exceptions in condition expressions gracefully and resorts to defaults when no state is active', () => {
123+
// given
124+
// given
125+
const marker = {sampleField: true, otherField: 'no state can become active now'};
126+
hostComponent.node = {children: [], name: 'sampleNode', root: null, marker: marker };
127+
hostComponent.states = sampleMarkerStates.slice(0);
128+
hostComponent.states.unshift({
129+
condition: (marker_) => marker_.nonExisting.property === true,
130+
cssClasses: 'brokenStateClass',
131+
label: (marker_) => { throw new Error('broken label provider'); },
132+
});
133+
134+
// when
135+
fixture.detectChanges();
136+
137+
// then
138+
const indicatorBoxTag = fixture.debugElement.query(By.css('div'));
139+
expect(indicatorBoxTag.nativeElement.className).toEqual('fa-fw');
140+
expect(indicatorBoxTag.nativeElement.attributes['title'].value).toEqual('');
141+
});
142+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Component, Input } from '@angular/core';
2+
import { MarkerState } from '../markers/marker.state';
3+
import { TreeNode } from '../tree-node';
4+
5+
@Component({
6+
selector: 'app-indicator-box',
7+
templateUrl: './indicator-box.component.html',
8+
styleUrls: ['./indicator-box.component.css']
9+
})
10+
export class IndicatorBoxComponent {
11+
@Input() model: { node: TreeNode, possibleStates: MarkerState[] };
12+
13+
get cssClasses(): string {
14+
if (this.isInitialized()) {
15+
const activeState = this.getActiveState();
16+
if (activeState != null) {
17+
return activeState.cssClasses + ' fa-fw';
18+
}
19+
}
20+
return 'fa-fw';
21+
}
22+
23+
get label(): string {
24+
if (this.isInitialized()) {
25+
const activeState = this.getActiveState();
26+
if (activeState) {
27+
return activeState.label(this.getMarker());
28+
}
29+
}
30+
return '';
31+
}
32+
33+
private getMarker(): any {
34+
return this.model.node.marker;
35+
}
36+
37+
private isInitialized(): boolean {
38+
return this.model != null && this.model.node != null && this.model.possibleStates != null;
39+
}
40+
41+
private getActiveState(): MarkerState {
42+
return this.model.possibleStates.find((state) => {
43+
try {
44+
return state.condition(this.getMarker());
45+
} catch (error) {
46+
console.log(error);
47+
return false;
48+
}
49+
});
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { MarkerState } from './marker.state';
2+
3+
describe('MarkerState', () => {
4+
5+
let sampleMarkerState: MarkerState;
6+
const sampleAttributeValue = 42;
7+
8+
beforeEach(() => {
9+
sampleMarkerState = {
10+
condition: (marker) => marker.someAttribute === sampleAttributeValue,
11+
cssClasses: 'someCssClass',
12+
label: (marker) => 'The label',
13+
};
14+
});
15+
16+
it('conditions can be invoked', () => {
17+
// given
18+
const marker = { someAttribute: sampleAttributeValue };
19+
20+
// when
21+
const actualResult = sampleMarkerState.condition(marker);
22+
23+
// then
24+
expect(actualResult).toBeTruthy();
25+
});
26+
27+
it('returns the correct label', () => {
28+
// given
29+
const markerState: MarkerState = {
30+
condition: () => true,
31+
cssClasses: '',
32+
label: (marker) => `label for ${marker.name}`,
33+
};
34+
const sampleMarker = { name: 'the marker' };
35+
36+
// when
37+
const actualLabel = markerState.label(sampleMarker);
38+
39+
// then
40+
expect(actualLabel).toEqual('label for the marker');
41+
});
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Represents the state a marker field can assume based on a marker.
3+
*
4+
* The marker objects provided as parameters to the condition and
5+
* the label functions are of arbitrary type: they will contain
6+
* attributes corresponding to whatever properties have been added
7+
* to the workspace element for which a field should be displayed.
8+
*/
9+
export class MarkerState {
10+
condition: (marker: any) => boolean;
11+
cssClasses: string;
12+
label: (marker: any) => string;
13+
}

src/app/modules/widgets/tree-viewer/tree-node.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface TreeNode {
1111
cssClasses?: string;
1212
hover?: string;
1313
id?: string;
14+
marker?: any;
1415
}
1516

1617

src/app/modules/widgets/tree-viewer/tree-viewer.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { NgModule } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { TreeViewerComponent } from './tree-viewer.component';
44
import { InputBoxComponent } from './input-box/input-box.component';
5+
import { IndicatorBoxComponent } from './indicator-box/indicator-box.component';
56

67
@NgModule({
78
imports: [
89
CommonModule
910
],
1011
declarations: [
1112
TreeViewerComponent,
12-
InputBoxComponent
13+
InputBoxComponent,
14+
IndicatorBoxComponent
1315
],
1416
exports: [
1517
TreeViewerComponent

0 commit comments

Comments
 (0)