Skip to content

Commit

Permalink
feat(viewer): resolve geoname id and show name (DSP-1212) (#305)
Browse files Browse the repository at this point in the history
* feat(viewer): resolve geoname id and show name

* feat(viewer): remove label input from geoname val comp

* test(viewer): add specs to geoname service

* test(app): mock component
  • Loading branch information
tobiasschweizer committed Jun 16, 2021
1 parent 0b03781 commit 18ea19b
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 35 deletions.
94 changes: 94 additions & 0 deletions projects/dsp-ui/src/lib/viewer/services/geoname.service.spec.ts
@@ -0,0 +1,94 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { GeonameService } from './geoname.service';
import { AppInitService } from '../../core';

describe('GeonameService', () => {
let service: GeonameService;
let httpTestingController: HttpTestingController;

const appInitSpy = {
config: {
geonameToken: 'token'
}
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
{
provide: AppInitService,
useValue: appInitSpy
}
]
});

service = TestBed.inject(GeonameService);
httpTestingController = TestBed.inject(HttpTestingController);
});

it('should be created', () => {
expect(service).toBeTruthy();
});

it('should resolve a given geoname id', done => {

service.resolveGeonameID('2661604').subscribe(
name => {
expect(name).toEqual('Basel');
done();
}
);

const httpRequest = httpTestingController.expectOne('https://ws.geonames.net/getJSON?geonameId=2661604&username=token&style=short');

expect(httpRequest.request.method).toEqual('GET');

const expectedResponse = { name: 'Basel' };

httpRequest.flush(expectedResponse);

});

it('should use the given geoname id as a fallback value if the requests fails', done => {

service.resolveGeonameID('2661604').subscribe(
name => {
expect(name).toEqual('2661604');
done();
}
);

const httpRequest = httpTestingController.expectOne('https://ws.geonames.net/getJSON?geonameId=2661604&username=token&style=short');

expect(httpRequest.request.method).toEqual('GET');

const mockErrorResponse = {status: 400, statusText: 'Bad Request'};

httpRequest.flush(mockErrorResponse);

});

it('should use the given geoname id as a fallback value if the requests response does not contain the expected information', done => {

service.resolveGeonameID('2661604').subscribe(
name => {
expect(name).toEqual('2661604');
done();
}
);

const httpRequest = httpTestingController.expectOne('https://ws.geonames.net/getJSON?geonameId=2661604&username=token&style=short');

expect(httpRequest.request.method).toEqual('GET');

const expectedResponse = { place: 'Basel' };

httpRequest.flush(expectedResponse);

});

});
38 changes: 38 additions & 0 deletions projects/dsp-ui/src/lib/viewer/services/geoname.service.ts
@@ -0,0 +1,38 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AppInitService } from '../../core';

@Injectable({
providedIn: 'root'
})
export class GeonameService {

constructor(
private readonly _http: HttpClient,
private _appInitService: AppInitService
) {
}

resolveGeonameID(id: string): Observable<string> {

return this._http.get<object>('https://ws.geonames.net/getJSON?geonameId=' + id + '&username=' + this._appInitService.config['geonameToken'] + '&style=short').pipe(
map(
(geo: { name: string }) => {

if (!('name' in geo)) {
throw 'no name property';
}

return geo.name;
}
),
catchError(error => {
// an error occurred, just return the id
return of(id);
})
);

}
}
@@ -1,6 +1,6 @@
<span *ngIf="mode === 'read'; else showForm" class="read-mode-view">
<span class="rm-value">
<a class="link" target="_blank" rel="noopener" href="https://www.geonames.org/{{valueFormControl.value}}">{{ label ? label : valueFormControl.value}}</a>
<a class="link" target="_blank" rel="noopener" href="https://www.geonames.org/{{valueFormControl.value}}">{{ $geonameLabel | async }}</a>
</span>
<span class="rm-comment" *ngIf="shouldShowComment">
{{ commentFormControl.value }}
Expand Down
Expand Up @@ -7,6 +7,8 @@ import { By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CreateGeonameValue, MockResource, ReadGeonameValue, UpdateGeonameValue } from '@dasch-swiss/dsp-js';
import { GeonameValueComponent } from './geoname-value.component';
import { GeonameService } from '../../services/geoname.service';
import { of } from 'rxjs';



Expand All @@ -15,7 +17,7 @@ import { GeonameValueComponent } from './geoname-value.component';
*/
@Component({
template: `
<dsp-geoname-value #inputVal [displayValue]="displayInputVal" [mode]="mode" [label]="label"></dsp-geoname-value>`
<dsp-geoname-value #inputVal [displayValue]="displayInputVal" [mode]="mode"></dsp-geoname-value>`
})
class TestHostDisplayValueComponent implements OnInit {

Expand All @@ -25,8 +27,6 @@ class TestHostDisplayValueComponent implements OnInit {

mode: 'read' | 'update' | 'create' | 'search';

label: string;

ngOnInit() {

MockResource.getTestThing().subscribe(res => {
Expand Down Expand Up @@ -63,6 +63,9 @@ class TestHostCreateValueComponent implements OnInit {

describe('GeonameValueComponent', () => {
beforeEach(waitForAsync(() => {

const mockGeonameService = jasmine.createSpyObj('GeonameService', ['resolveGeonameID']);

TestBed.configureTestingModule({
declarations: [
GeonameValueComponent,
Expand All @@ -75,6 +78,10 @@ describe('GeonameValueComponent', () => {
BrowserAnimationsModule,
MatIconModule
],
providers: [{
provide: GeonameService,
useValue: mockGeonameService
}]
})
.compileComponents();
}));
Expand All @@ -89,6 +96,10 @@ describe('GeonameValueComponent', () => {
let valueReadModeNativeElement;

beforeEach(() => {
const geonameServiceMock = TestBed.inject(GeonameService) as jasmine.SpyObj<GeonameService>;

geonameServiceMock.resolveGeonameID.and.returnValue(of('Basel'));

testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
Expand All @@ -104,43 +115,25 @@ describe('GeonameValueComponent', () => {

it('should display an existing value', () => {

const geonameServiceMock = TestBed.inject(GeonameService) as jasmine.SpyObj<GeonameService>;

expect(testHostComponent.inputValueComponent.displayValue.geoname).toEqual('2661604');

expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();

expect(testHostComponent.inputValueComponent.mode).toEqual('read');

expect(valueReadModeNativeElement.innerText).toEqual('2661604');
expect(valueReadModeNativeElement.innerText).toEqual('Basel');

const anchorDebugElement = valueReadModeDebugElement.query(By.css('a'));
expect(anchorDebugElement.nativeElement).toBeDefined();

expect(anchorDebugElement.attributes.href).toEqual('https://www.geonames.org/2661604');
expect(anchorDebugElement.attributes.target).toEqual('_blank');

});

it('should display an existing value with a label', () => {

testHostComponent.label = 'testlabel';

testHostFixture.detectChanges();
expect(geonameServiceMock.resolveGeonameID).toHaveBeenCalledOnceWith('2661604');

expect(testHostComponent.inputValueComponent.displayValue.geoname).toEqual('2661604');

expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();

expect(testHostComponent.inputValueComponent.mode).toEqual('read');

expect(valueReadModeNativeElement.innerText).toEqual('testlabel');

const anchorDebugElement = valueReadModeDebugElement.query(By.css('a'));
expect(anchorDebugElement.nativeElement).toBeDefined();

expect(anchorDebugElement.attributes.href).toEqual('https://www.geonames.org/2661604');
expect(anchorDebugElement.attributes.target).toEqual('_blank');

});
});

it('should make an existing value editable', () => {

Expand Down Expand Up @@ -233,6 +226,10 @@ describe('GeonameValueComponent', () => {

it('should set a new display value', () => {

const geonameServiceMock = TestBed.inject(GeonameService) as jasmine.SpyObj<GeonameService>;

geonameServiceMock.resolveGeonameID.and.returnValue(of('Terra Linda High School'));

const newStr = new ReadGeonameValue();

newStr.geoname = '5401678';
Expand All @@ -242,10 +239,12 @@ describe('GeonameValueComponent', () => {

testHostFixture.detectChanges();

expect(valueReadModeNativeElement.innerText).toEqual('5401678');
expect(valueReadModeNativeElement.innerText).toEqual('Terra Linda High School');

expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy();

expect(geonameServiceMock.resolveGeonameID).toHaveBeenCalledWith('5401678');

});

it('should unsubscribe when destroyed', () => {
Expand Down
@@ -1,10 +1,11 @@
import { Component, Inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { CreateGeonameValue, ReadGeonameValue, UpdateGeonameValue } from '@dasch-swiss/dsp-js';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { BaseValueComponent } from '../base-value.component';
import { CustomRegex } from '../custom-regex';
import { ValueErrorStateMatcher } from '../value-error-state-matcher';
import { GeonameService } from '../../services/geoname.service';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
const resolvedPromise = Promise.resolve(null);
Expand All @@ -16,7 +17,6 @@ const resolvedPromise = Promise.resolve(null);
})
export class GeonameValueComponent extends BaseValueComponent implements OnInit, OnChanges, OnDestroy {
@Input() displayValue?: ReadGeonameValue;
@Input() label?: string;

valueFormControl: FormControl;
commentFormControl: FormControl;
Expand All @@ -27,7 +27,9 @@ export class GeonameValueComponent extends BaseValueComponent implements OnInit,
matcher = new ValueErrorStateMatcher();
customValidators = [Validators.pattern(CustomRegex.GEONAME_REGEX)];

constructor(@Inject(FormBuilder) private _fb: FormBuilder) {
$geonameLabel: Observable<string>;

constructor(@Inject(FormBuilder) private _fb: FormBuilder, private _geonameService: GeonameService) {
super();
}

Expand Down Expand Up @@ -60,6 +62,10 @@ export class GeonameValueComponent extends BaseValueComponent implements OnInit,

this.resetFormControl();

if (this.mode === 'read') {
this.$geonameLabel = this._geonameService.resolveGeonameID(this.valueFormControl.value);
}

resolvedPromise.then(() => {
// add form to the parent form group
this.addToParentFormGroup(this.formName, this.form);
Expand All @@ -71,6 +77,10 @@ export class GeonameValueComponent extends BaseValueComponent implements OnInit,
// resets values and validators in form controls when input displayValue or mode changes
// at the first call of ngOnChanges, form control elements are not initialized yet
this.resetFormControl();

if (this.mode === 'read' && this.valueFormControl !== undefined) {
this.$geonameLabel = this._geonameService.resolveGeonameID(this.valueFormControl.value);
}
}

ngOnDestroy(): void {
Expand Down
16 changes: 14 additions & 2 deletions src/app/viewer-playground/viewer-playground.component.spec.ts
Expand Up @@ -13,6 +13,18 @@ import { DspApiConnectionToken, DspViewerModule, UserService } from '@dasch-swis
import { ViewerPlaygroundComponent } from './viewer-playground.component';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Component, Input } from '@angular/core';

@Component({
selector: `dsp-resource-view`,
template: ``
})
class TestResourveViewComponent {

@Input() iri: string;

@Input() showToolbar: boolean;
}

describe('ViewerPlaygroundComponent', () => {
let component: ViewerPlaygroundComponent;
Expand All @@ -33,10 +45,10 @@ describe('ViewerPlaygroundComponent', () => {

TestBed.configureTestingModule({
declarations: [
ViewerPlaygroundComponent
ViewerPlaygroundComponent,
TestResourveViewComponent
],
imports: [
DspViewerModule,
MatIconModule,
MatTooltipModule
],
Expand Down
3 changes: 2 additions & 1 deletion src/config/config.dev.json
Expand Up @@ -5,5 +5,6 @@
"apiPath": "",
"jsonWebToken": "",
"logErrors": true,
"sipiUrl": "http://localhost:1024/"
"sipiUrl": "http://localhost:1024/",
"geonameToken": "knora"
}
3 changes: 2 additions & 1 deletion src/config/config.prod.json
Expand Up @@ -5,5 +5,6 @@
"apiPath": "",
"jsonWebToken": "",
"logErrors": false,
"sipiUrl": "https://iiif.dasch.swiss:1024/"
"sipiUrl": "https://iiif.dasch.swiss:1024/",
"geonameToken": "knora"
}

0 comments on commit 18ea19b

Please sign in to comment.