Skip to content

Commit

Permalink
feat(list-editor): add deletion functionality (DSP-1334) (#378)
Browse files Browse the repository at this point in the history
* feat(list-editor): adds deletion functionality

* feat: adds simple error handling

* test: adds unit tests

* test: adds even more unit tests!

* chore: adds comments to explain some code

* refactor: small name change

* fix: list of children nodes are now correctly updated in the parent list node

* chore: remove package-lock.json
  • Loading branch information
mdelez committed Feb 16, 2021
1 parent 2debd03 commit 34c74a6
Show file tree
Hide file tree
Showing 9 changed files with 471 additions and 108 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 29 additions & 2 deletions src/app/main/dialog/dialog.component.html
Expand Up @@ -160,10 +160,37 @@
<app-edit-list-item [iri]="data.id" [projectIri]="data.project" (closeDialog)="dialogRef.close($event)"></app-edit-list-item>
</div>

<!-- Delete list node-->
<div *ngSwitchCase="'deleteListNode'">
<app-dialog-header [title]="data.title" [subtitle]="'appLabels.form.lists.title.deleteListNode' | translate"></app-dialog-header>
Do you want to delete this node?
<mat-dialog-actions>
<button mat-button mat-dialog-close class="cancel-button center" [mat-dialog-close]="false">
Cancel
</button>
<span class="fill-remaining-space"></span>
<button mat-button mat-raised-button [color]="'warn'" class="confirm-button center"
[mat-dialog-close]="true">
Delete
</button>
</mat-dialog-actions>
</div>

<!-- Delete list node error-->
<div *ngSwitchCase="'deleteListNodeError'">
Unable to delete this list node as it is in use.
<mat-dialog-actions>
<span class="fill-remaining-space"></span>
<button mat-button mat-raised-button [color]="'warn'" class="confirm-button center"
[mat-dialog-close]="true">
Okay
</button>
</mat-dialog-actions>
</div>

<!-- Delete list -->
<div *ngSwitchCase="'deleteList'">
<app-dialog-header [title]="data.title" [subtitle]="'appLabels.form.lists.title.delete' | translate">
</app-dialog-header>
<app-dialog-header [title]="data.title" [subtitle]="'appLabels.form.lists.title.deleteList' | translate"></app-dialog-header>
Do you want to delete this list?
<mat-dialog-actions>
<button mat-button mat-dialog-close class="cancel-button center" [mat-dialog-close]="false">
Expand Down
Expand Up @@ -29,6 +29,12 @@
(click)="$event.stopPropagation(); openDialog('editListNode', labels[0].value, iri)">
<mat-icon>edit</mat-icon>
</button>
<button mat-button
class="delete"
title="delete"
(click)="$event.stopPropagation(); openDialog('deleteListNode', labels[0].value, iri)">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>
176 changes: 144 additions & 32 deletions src/app/project/list/list-item-form/list-item-form.component.spec.ts
@@ -1,69 +1,181 @@
import { HttpClientModule } from '@angular/common/http';
import { OverlayContainer } from '@angular/cdk/overlay';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { Component, OnInit, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { KnoraApiConnection } from '@dasch-swiss/dsp-js';
import { ApiResponseData, DeleteListNodeResponse, ListsEndpointAdmin, StringLiteral } from '@dasch-swiss/dsp-js';
import {
AppInitService,
DspActionModule,
DspApiConfigToken,
DspApiConnectionToken,
DspCoreModule
DspApiConnectionToken
} from '@dasch-swiss/dsp-ui';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
import { AjaxResponse } from 'rxjs/ajax';
import { DialogHeaderComponent } from 'src/app/main/dialog/dialog-header/dialog-header.component';
import { DialogComponent } from 'src/app/main/dialog/dialog.component';
import { ErrorComponent } from 'src/app/main/error/error.component';
import { TestConfig } from 'test.config';
import { ListItemFormComponent } from './list-item-form.component';
import { ListItemFormComponent, ListNodeOperation } from './list-item-form.component';

/**
* Test host component to simulate parent component.
*/
@Component({
template: `
<app-list-item-form
#listItemForm
[iri]="iri"
[language]="language"
(refreshParent)="updateView($event)"
[projectIri]="projectIri"
[projectcode]="projectCode"
[labels]="labels">
</app-list-item-form>`
})
class TestHostComponent implements OnInit {

@ViewChild('listItemForm') listItemForm: ListItemFormComponent;

iri = 'http://rdfh.ch/lists/0001/notUsedList01';

language = 'en';

projectIri = 'http://rdfh.ch/projects/0001';

projectCode = '0001';

labels: StringLiteral[];

constructor() {}

ngOnInit() {
this.labels = [
{
value: 'node 1',
language: 'en'
}
];
}

}

describe('ListItemFormComponent', () => {
let component: ListItemFormComponent;
let fixture: ComponentFixture<ListItemFormComponent>;
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
let rootLoader: HarnessLoader;
let overlayContainer: OverlayContainer;

beforeEach(async(() => {

const listsEndpointSpyObj = {
admin: {
listsEndpoint: jasmine.createSpyObj('listsEndpoint', ['deleteListNode'])
}
};

TestBed.configureTestingModule({
declarations: [
ListItemFormComponent,
TestHostComponent,
DialogComponent,
ErrorComponent
DialogHeaderComponent
],
imports: [
BrowserAnimationsModule,
DspActionModule,
DspCoreModule,
HttpClientModule,
MatIconModule,
MatInputModule,
ReactiveFormsModule,
RouterTestingModule,
MatDialogModule,
MatButtonModule,
TranslateModule.forRoot()
],
providers: [
AppInitService,
{
provide: DspApiConfigToken,
useValue: TestConfig.ApiConfig
provide: DspApiConnectionToken,
useValue: listsEndpointSpyObj
},
{
provide: DspApiConnectionToken,
useValue: new KnoraApiConnection(TestConfig.ApiConfig)
}
provide: MAT_DIALOG_DATA,
useValue: {}
},
{
provide: MatDialogRef,
useValue: {}
},
]

})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ListItemFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
testHostFixture = TestBed.createComponent(TestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();

expect(testHostComponent).toBeTruthy();

testHostComponent.listItemForm.showActionBubble = true;
testHostFixture.detectChanges();

overlayContainer = TestBed.inject(OverlayContainer);
rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture);
});

it('should create', () => {
expect(component).toBeTruthy();
afterEach(async () => {
const dialogs = await rootLoader.getAllHarnesses(MatDialogHarness);
await Promise.all(dialogs.map(async d => await d.close()));

// angular won't call this for us so we need to do it ourselves to avoid leaks.
overlayContainer.ngOnDestroy();
});

it('should show a dialog box when the delete button is clicked', async () => {

const deleteListNodeResponse: DeleteListNodeResponse = new DeleteListNodeResponse();
deleteListNodeResponse.node.children = [];
deleteListNodeResponse.node.id = 'http://rdfh.ch/lists/0001/notUsedList';
deleteListNodeResponse.node.isRootNode = true;
deleteListNodeResponse.node.name = 'notUsedList';
deleteListNodeResponse.node.projectIri = 'http://rdfh.ch/projects/0001';

const listSpy = TestBed.inject(DspApiConnectionToken);

(listSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).deleteListNode.and.callFake(
() => {

const response = deleteListNodeResponse;

return of(ApiResponseData.fromAjaxResponse({response} as AjaxResponse));
}
);

spyOn(testHostComponent.listItemForm.refreshParent, 'emit');

const deleteButton = await rootLoader.getHarness(MatButtonHarness.with({selector: '.delete'}));
await deleteButton.click();

const dialogHarnesses = await rootLoader.getAllHarnesses(MatDialogHarness);

expect(dialogHarnesses.length).toEqual(1);

const confirmButton = await rootLoader.getHarness(MatButtonHarness.with({selector: '.confirm-button'}));

await confirmButton.click();

const listNodeOperation: ListNodeOperation = new ListNodeOperation();
listNodeOperation.listNode = deleteListNodeResponse.node;
listNodeOperation.operation = 'delete';

testHostFixture.whenStable().then(() => {
expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledWith(testHostComponent.iri);
expect(listSpy.admin.listsEndpoint.deleteListNode).toHaveBeenCalledTimes(1);

expect(testHostComponent.listItemForm.refreshParent.emit).toHaveBeenCalledWith(listNodeOperation);
expect(testHostComponent.listItemForm.refreshParent.emit).toHaveBeenCalledTimes(1);
});

});
});

0 comments on commit 34c74a6

Please sign in to comment.