Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat(list editor): Adds support for editing lists (DSP-741) (#365)
* feat(list editor): Adds support for editing lists * feat(list editor): Adds support for editing lists * feat(list editor): updates label placeholder when updating labels * test(edit-list-item): adds unit tests * test: cleanup and add additional test for form validation * chore: adds method descriptions and updates a label to use the translation service * chore: adds more method descriptions
- Loading branch information
Showing
11 changed files
with
565 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<dsp-progress-indicator *ngIf="loading"></dsp-progress-indicator> | ||
|
||
<div *ngIf="!loading" class="form-content list-info"> | ||
|
||
<!-- list label --> | ||
<dsp-string-literal-input | ||
[placeholder]="'Child node label'" | ||
[value]="labels" | ||
(dataChanged)="handleData($event, 'labels')"> | ||
</dsp-string-literal-input> | ||
<span class="invalid-form" *ngIf="formInvalidMessage">{{ formInvalidMessage }}</span> | ||
|
||
<br><br> | ||
|
||
<!-- list description / comment --> | ||
<dsp-string-literal-input | ||
[textarea]="true" | ||
[placeholder]="'Child node description'" | ||
[value]="comments" | ||
(dataChanged)="handleData($event, 'comments')" | ||
[language]="labels.length ? labels[0].language : 'en'"> | ||
</dsp-string-literal-input> | ||
|
||
<div class="form-panel form-action"> | ||
<span> | ||
<button | ||
mat-button | ||
type="button" | ||
(click)="closeDialog.emit()"> | ||
{{ 'appLabels.form.action.cancel' | translate }} | ||
</button> | ||
</span> | ||
<span class="fill-remaining-space"></span> | ||
<span> | ||
<button | ||
mat-raised-button | ||
type="submit" | ||
color="primary" | ||
[disabled]="saveButtonDisabled" | ||
(click)="updateChildNode()"> | ||
{{ 'appLabels.form.action.update' | translate }} | ||
</button> | ||
</span> | ||
</div> | ||
</div> |
4 changes: 4 additions & 0 deletions
4
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.invalid-form{ | ||
color: red; | ||
font-size: 11px; | ||
} |
152 changes: 152 additions & 0 deletions
152
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { Component, DebugElement, OnInit, ViewChild } from '@angular/core'; | ||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { By } from '@angular/platform-browser'; | ||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | ||
import { ApiResponseData, ListNodeInfoResponse, ListsEndpointAdmin, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; | ||
import { DspActionModule, DspApiConnectionToken, ProgressIndicatorComponent } from '@dasch-swiss/dsp-ui'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
import { of } from 'rxjs'; | ||
import { AjaxResponse } from 'rxjs/ajax'; | ||
import { EditListItemComponent } from './edit-list-item.component'; | ||
|
||
/** | ||
* Test host component to simulate parent component. | ||
*/ | ||
@Component({ | ||
template: `<app-edit-list-item #editListItem [iri]="iri" [projectIri]="projectIri"></app-edit-list-item>` | ||
}) | ||
class TestHostComponent implements OnInit { | ||
|
||
@ViewChild('editListItem') editListItem: EditListItemComponent; | ||
|
||
iri = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
|
||
projectIri = 'http://rdfh.ch/projects/0001'; | ||
|
||
constructor() {} | ||
|
||
ngOnInit() { | ||
} | ||
|
||
} | ||
|
||
describe('EditListItemComponent', () => { | ||
let testHostComponent: TestHostComponent; | ||
let testHostFixture: ComponentFixture<TestHostComponent>; | ||
let editListItemComponentDe: DebugElement; | ||
let formInvalidMessageDe: DebugElement; | ||
|
||
beforeEach(async(() => { | ||
|
||
const listsEndpointSpyObj = { | ||
admin: { | ||
listsEndpoint: jasmine.createSpyObj('listsEndpoint', ['getListNodeInfo', 'updateChildNode']) | ||
} | ||
}; | ||
|
||
TestBed.configureTestingModule({ | ||
declarations: [ | ||
EditListItemComponent, | ||
TestHostComponent, | ||
ProgressIndicatorComponent, | ||
], | ||
imports: [ | ||
BrowserAnimationsModule, | ||
DspActionModule, | ||
TranslateModule.forRoot() | ||
], | ||
providers: [ | ||
{ | ||
provide: DspApiConnectionToken, | ||
useValue: listsEndpointSpyObj | ||
} | ||
] | ||
}) | ||
.compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
|
||
(dspConnSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).getListNodeInfo.and.callFake( | ||
() => { | ||
const response = new ListNodeInfoResponse(); | ||
response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}]; | ||
response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}]; | ||
return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); | ||
} | ||
); | ||
|
||
testHostFixture = TestBed.createComponent(TestHostComponent); | ||
testHostComponent = testHostFixture.componentInstance; | ||
testHostFixture.detectChanges(); | ||
|
||
expect(testHostComponent).toBeTruthy(); | ||
|
||
const hostCompDe = testHostFixture.debugElement; | ||
editListItemComponentDe = hostCompDe.query(By.directive(EditListItemComponent)); | ||
expect(editListItemComponentDe).toBeTruthy(); | ||
}); | ||
|
||
it('should assign labels and comments', () => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); | ||
expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledTimes(1); | ||
expect(dspConnSpy.admin.listsEndpoint.getListNodeInfo).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/otherTreeList01'); | ||
|
||
}); | ||
|
||
it('should update labels when the value changes', () => { | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}]); | ||
testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); | ||
expect(testHostComponent.editListItem.labels).toEqual([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
testHostComponent.editListItem.handleData([], 'labels'); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeTruthy(); | ||
testHostFixture.detectChanges(); | ||
formInvalidMessageDe = editListItemComponentDe.query(By.css('span.invalid-form')); | ||
expect(formInvalidMessageDe.nativeElement.innerText).toEqual('A label is required.'); | ||
}); | ||
|
||
it('should update comments when the value changes', () => { | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}]); | ||
testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); | ||
expect(testHostComponent.editListItem.comments).toEqual([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
testHostComponent.editListItem.handleData([], 'comments'); | ||
expect(testHostComponent.editListItem.saveButtonDisabled).toBeFalsy(); | ||
}); | ||
|
||
it('should update the child node info', () => { | ||
const dspConnSpy = TestBed.inject(DspApiConnectionToken); | ||
|
||
testHostComponent.editListItem.handleData([{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}], 'labels'); | ||
testHostComponent.editListItem.handleData([{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}], 'comments'); | ||
|
||
(dspConnSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).updateChildNode.and.callFake( | ||
() => { | ||
const response = new ListNodeInfoResponse(); | ||
response.nodeinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList01'; | ||
response.nodeinfo.labels = [{'value': 'Tree list node 01', 'language': 'en'}, {'value': 'Baumlistenknoten 01', 'language': 'de'}]; | ||
response.nodeinfo.comments = [{'value': 'My comment', 'language': 'en'}, {'value': 'Mein Kommentar', 'language': 'de'}]; | ||
|
||
expect(testHostComponent.editListItem.labels).toEqual(response.nodeinfo.labels); | ||
expect(testHostComponent.editListItem.comments).toEqual(response.nodeinfo.comments); | ||
|
||
return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse)); | ||
} | ||
); | ||
|
||
const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); | ||
childNodeUpdateData.projectIri = testHostComponent.editListItem.projectIri; | ||
childNodeUpdateData.listIri = testHostComponent.editListItem.iri; | ||
childNodeUpdateData.labels = testHostComponent.editListItem.labels; | ||
childNodeUpdateData.comments = testHostComponent.editListItem.comments; | ||
|
||
testHostComponent.editListItem.updateChildNode(); | ||
expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledTimes(1); | ||
expect(dspConnSpy.admin.listsEndpoint.updateChildNode).toHaveBeenCalledWith(childNodeUpdateData); | ||
}); | ||
}); |
130 changes: 130 additions & 0 deletions
130
src/app/project/list/list-item-form/edit-list-item/edit-list-item.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; | ||
import { ApiResponseData, ApiResponseError, ChildNodeInfoResponse, KnoraApiConnection, List, ListNodeInfo, ListNodeInfoResponse, StringLiteral, UpdateChildNodeRequest } from '@dasch-swiss/dsp-js'; | ||
import { DspApiConnectionToken } from '@dasch-swiss/dsp-ui'; | ||
|
||
@Component({ | ||
selector: 'app-edit-list-item', | ||
templateUrl: './edit-list-item.component.html', | ||
styleUrls: ['./edit-list-item.component.scss'] | ||
}) | ||
export class EditListItemComponent implements OnInit { | ||
loading: boolean; | ||
|
||
@Input() iri: string; | ||
|
||
@Input() projectIri: string; | ||
|
||
@Output() closeDialog: EventEmitter<List | ListNodeInfo> = new EventEmitter<List>(); | ||
|
||
// the list node being edited | ||
listNode: ListNodeInfo; | ||
|
||
// local arrays to use when updating the list node | ||
labels: StringLiteral[]; | ||
comments: StringLiteral[]; | ||
|
||
/** | ||
* error checking on the following fields | ||
*/ | ||
formErrors = { | ||
label: { | ||
'required': 'A label is required.' | ||
} | ||
}; | ||
|
||
/** | ||
* in case of an API error | ||
*/ | ||
errorMessage: any; | ||
|
||
saveButtonDisabled = false; | ||
|
||
formInvalidMessage: string; | ||
|
||
constructor(@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection) { } | ||
|
||
ngOnInit(): void { | ||
this.loading = true; | ||
|
||
// get list | ||
this._dspApiConnection.admin.listsEndpoint.getListNodeInfo(this.iri).subscribe( | ||
(response: ApiResponseData<ListNodeInfoResponse>) => { | ||
this.listNode = response.body.nodeinfo; | ||
this.buildForm(response.body.nodeinfo); | ||
}, | ||
(error: ApiResponseError) => { | ||
console.error(error); | ||
} | ||
); | ||
} | ||
|
||
/** | ||
* Separates the labels and comments of a list node into two local arrays. | ||
* | ||
* @param listNode info about a list node | ||
*/ | ||
buildForm(listNode: ListNodeInfo): void { | ||
|
||
this.labels = []; | ||
this.comments = []; | ||
|
||
if (listNode && listNode.id) { | ||
this.labels = listNode.labels; | ||
this.comments = listNode.comments; | ||
} | ||
|
||
this.loading = false; | ||
} | ||
|
||
/** | ||
* Called from the template any time the labels or comments are changed to update the local arrays. | ||
* At least one label is required. Otherwise, the 'update' button will be disabled. | ||
* | ||
* @param data the data that was changed | ||
* @param type the type of data that was changed | ||
*/ | ||
handleData(data: StringLiteral[], type: string) { | ||
switch (type) { | ||
case 'labels': | ||
this.labels = data; | ||
break; | ||
|
||
case 'comments': | ||
this.comments = data; | ||
break; | ||
} | ||
|
||
if (this.labels.length === 0) { | ||
// invalid form, don't let user submit | ||
this.saveButtonDisabled = true; | ||
this.formInvalidMessage = this.formErrors.label.required; | ||
} else { | ||
this.saveButtonDisabled = false; | ||
this.formInvalidMessage = null; | ||
} | ||
} | ||
|
||
/** | ||
* Called from the template when the 'update' button is clicked. | ||
* Sends a request to DSP-API to update the list node with the data inside the two local arrays. | ||
*/ | ||
updateChildNode() { | ||
const childNodeUpdateData: UpdateChildNodeRequest = new UpdateChildNodeRequest(); | ||
childNodeUpdateData.projectIri = this.projectIri; | ||
childNodeUpdateData.listIri = this.iri; | ||
childNodeUpdateData.labels = this.labels; | ||
childNodeUpdateData.comments = this.comments.length > 0 ? this.comments : []; | ||
|
||
this._dspApiConnection.admin.listsEndpoint.updateChildNode(childNodeUpdateData).subscribe( | ||
(response: ApiResponseData<ChildNodeInfoResponse>) => { | ||
this.loading = false; | ||
this.closeDialog.emit(response.body.nodeinfo); | ||
}, | ||
(error: ApiResponseError) => { | ||
this.errorMessage = error; | ||
this.loading = false; | ||
} | ||
); | ||
} | ||
|
||
} |
Oops, something went wrong.