Skip to content

Commit

Permalink
refactor(list-editor): new list form refactor (DSP-1392) (#403)
Browse files Browse the repository at this point in the history
* feat(list-editor): refactor to prepare for new list form refactor

* refactor(list-editor): refactors the create new list form to a single form

* test(list-info-form): adds beautiful unit tests

* fix(list-editor): small fixes

* refactor(list-editor): remove setTimeout

* fix: small changes to make ESlint happy

* fix: update variable name in template

* fix: update more labels

* fix: fixes logic to disable the plus button for a new node if the label is empty
  • Loading branch information
mdelez committed Mar 5, 2021
1 parent 17e0e1a commit 8824682
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 171 deletions.
10 changes: 5 additions & 5 deletions src/app/main/dialog/dialog.component.html
Expand Up @@ -140,14 +140,14 @@

<!-- Create new list -->
<div *ngSwitchCase="'createList'">
<app-dialog-header [title]="data.title" [subtitle]="'Create new'"></app-dialog-header>
<app-list-info-form [projectcode]="data.project" [projectIri]="data.id" (closeDialog)="dialogRef.close()"
(updateParent)="replaceTitle($event)"></app-list-info-form>
<app-dialog-header [title]="'Create new list'" [subtitle]=""></app-dialog-header>
<app-list-info-form [projectcode]="data.project" [mode]="'create'" [projectIri]="data.id" (closeDialog)="dialogRef.close($event)">
</app-list-info-form>
</div>

<!-- Insert list child node -->
<div *ngSwitchCase="'insertListNode'">
<app-dialog-header [title]="data.title" [subtitle]="'Insert new child node'">
<app-dialog-header [title]="'Insert new child node'" [subtitle]="">
</app-dialog-header>
<app-edit-list-item [iri]="data.id" [projectIri]="data.project" mode="insert" [projectCode]="data.projectCode" [parentIri]="data.parentIri" [position]="data.position" (closeDialog)="dialogRef.close($event)"></app-edit-list-item>
</div>
Expand All @@ -156,7 +156,7 @@
<div *ngSwitchCase="'editListInfo'">
<app-dialog-header [title]="data.title" [subtitle]="'Edit list info'">
</app-dialog-header>
<app-list-info-form [iri]="data.id" [projectIri]="data.project" (closeDialog)="dialogRef.close()">
<app-list-info-form [iri]="data.id" [mode]="'update'" [projectIri]="data.project" (closeDialog)="dialogRef.close()">
</app-list-info-form>
</div>

Expand Down
21 changes: 4 additions & 17 deletions src/app/project/list/list-info-form/list-info-form.component.html
@@ -1,19 +1,13 @@
<div>

<dsp-progress-indicator *ngIf="loading"></dsp-progress-indicator>

<!-- success message after updating -->
<dsp-message *ngIf="success" [message]="successMessage" [short]="true"></dsp-message>

<div *ngIf="!loading && !createList" class="form-content list-info">
<div *ngIf="!loading" class="form-content list-info">

<!-- list label -->
<dsp-string-literal-input [placeholder]="'List label'" [value]="labels"
(dataChanged)="handleData($event, 'labels')">
</dsp-string-literal-input>
<!-- TODO: we should support mat hint in the string literal input <mat-hint *ngIf="formErrors.label">
{{ formErrors.label }}
</mat-hint> -->
<mat-hint class="invalid-form" *ngIf="labelInvalidMessage">{{ labelInvalidMessage }}</mat-hint>

<br><br>

Expand All @@ -31,17 +25,10 @@
<span class="fill-remaining-space"></span>
<span>
<button mat-raised-button type="submit" color="primary" [disabled]="labels.length === 0"
(click)="submitData()">
{{ iri ? 'Update' : 'Next' }}
(click)="submitData()" class="list-submit">
{{ mode === 'update' ? 'Update' : 'Create' | translate }}
</button>
</span>
</div>
</div>

<div class="create-list-items" *ngIf="!loading && createList && newList">
<app-list-item [projectcode]="projectcode" [projectIri]="projectIri" [parentIri]="newList.listinfo.id"
[language]="labels[0].language">
</app-list-item>
</div>

</div>
234 changes: 213 additions & 21 deletions src/app/project/list/list-info-form/list-info-form.component.spec.ts
@@ -1,37 +1,88 @@
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HttpClientModule } from '@angular/common/http';
import { Component, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
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, CreateListRequest, ListInfoResponse, ListResponse, ListsEndpointAdmin, UpdateListInfoRequest } from '@dasch-swiss/dsp-js';
import {
AppInitService,
DspActionModule,
DspApiConfigToken,
DspApiConnectionToken,
DspCoreModule
} 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/list-item-form.component';
import { ListItemComponent } from '../list-item/list-item.component';
import { ListInfoFormComponent } from './list-info-form.component';

/**
* test host component to simulate parent component for creating a new list.
*/
@Component({
template: '<app-list-info-form #listInfoForm [iri]="iri" [mode]="mode" [projectIri]="projectIri"></app-list-info-form>'
})
class TestHostUpdateListComponent {

@ViewChild('listInfoForm') listInfoForm: ListInfoFormComponent;

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

mode = 'update';

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

constructor() {}
}

/**
* test host component to simulate parent component for creating a new list.
*/
@Component({
template: '<app-list-info-form #listInfoForm [mode]="mode" [projectcode]="projectcode" [projectIri]="projectIri"></app-list-info-form>'
})
class TestHostCreateListComponent {

@ViewChild('listInfoForm') listInfoForm: ListInfoFormComponent;

mode = 'create';

projectcode = '0001';

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

constructor() {}
}

describe('ListInfoFormComponent', () => {
let component: ListInfoFormComponent;
let fixture: ComponentFixture<ListInfoFormComponent>;
let testHostUpdateListComponent: TestHostUpdateListComponent;
let testHostUpdateListFixture: ComponentFixture<TestHostUpdateListComponent>;
let testHostCreateListComponent: TestHostCreateListComponent;
let testHostCreateListFixture: ComponentFixture<TestHostCreateListComponent>;
let rootLoader: HarnessLoader;

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

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TestHostUpdateListComponent,
TestHostCreateListComponent,
ListInfoFormComponent,
ListItemComponent,
ListItemFormComponent,
DialogComponent,
DialogHeaderComponent,
ErrorComponent
],
imports: [
Expand All @@ -46,27 +97,168 @@ describe('ListInfoFormComponent', () => {
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(ListInfoFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
describe('update existing list info', () => {
beforeEach(() => {

const listsEndpointSpy = TestBed.inject(DspApiConnectionToken);

(listsEndpointSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).getListInfo.and.callFake(
() => {
const response = new ListInfoResponse();
response.listinfo.id = 'http://rdfh.ch/lists/0001/otherTreeList';
response.listinfo.labels = [{ 'value': 'Other Tree List', 'language': 'en' }];
response.listinfo.comments = [{ 'value': 'Other Tree List comment', 'language': 'en' }];
return of(ApiResponseData.fromAjaxResponse({ response } as AjaxResponse));
}
);

testHostUpdateListFixture = TestBed.createComponent(TestHostUpdateListComponent);
testHostUpdateListComponent = testHostUpdateListFixture.componentInstance;
testHostUpdateListFixture.detectChanges();
expect(testHostUpdateListComponent).toBeTruthy();

rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostUpdateListFixture);
});

it('should instantiate arrays for labels and comments', () => {
expect(testHostUpdateListComponent.listInfoForm.labels).toEqual([{ 'value': 'Other Tree List', 'language': 'en' }]);
expect(testHostUpdateListComponent.listInfoForm.comments).toEqual([{ 'value': 'Other Tree List comment', 'language': 'en' }]);
});

it('should display "Update" as the submit button text and be disabled as long as no labels are provided', async () => {
const submitButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.list-submit' }));

expect(await submitButton.getText()).toEqual('Update');

expect(await submitButton.isDisabled()).toBeFalsy();

testHostUpdateListComponent.listInfoForm.handleData([], 'labels');

expect(await submitButton.isDisabled()).toBeTruthy();

testHostUpdateListComponent.listInfoForm.handleData([{ 'value': 'My edited list label', 'language': 'en' }], 'labels');

expect(await submitButton.isDisabled()).toBeFalsy();
});

it('should update labels when the value changes', () => {
testHostUpdateListComponent.listInfoForm.handleData([{ 'value': 'My edited list label', 'language': 'en' }], 'labels');
expect(testHostUpdateListComponent.listInfoForm.labels).toEqual([{ 'value': 'My edited list label', 'language': 'en' }]);
});

it('should update comments when the value changes', () => {
testHostUpdateListComponent.listInfoForm.handleData([{ 'value': 'My edited list comment', 'language': 'en' }], 'comments');
expect(testHostUpdateListComponent.listInfoForm.comments).toEqual([{ 'value': 'My edited list comment', 'language': 'en' }]);
});

it('should update the list info', () => {
const listsEndpointSpy = TestBed.inject(DspApiConnectionToken);

testHostUpdateListComponent.listInfoForm.handleData([{ 'value': 'My edited list label', 'language': 'en' }], 'labels');
testHostUpdateListComponent.listInfoForm.handleData([{ 'value': 'My edited list comment', 'language': 'en' }], 'comments');

const updateListInfoRequest: UpdateListInfoRequest = new UpdateListInfoRequest();
updateListInfoRequest.listIri = testHostUpdateListComponent.listInfoForm.iri;
updateListInfoRequest.projectIri = testHostUpdateListComponent.listInfoForm.projectIri;
updateListInfoRequest.labels = testHostUpdateListComponent.listInfoForm.labels;
updateListInfoRequest.comments = testHostUpdateListComponent.listInfoForm.comments;

(listsEndpointSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).updateListInfo.and.callFake(
() => {
const response = new ListInfoResponse();
response.listinfo.labels = [{ 'value': 'My edited list label', 'language': 'en' }];
response.listinfo.comments = [{ 'value': 'My edited list comment', 'language': 'en' }];

expect(updateListInfoRequest.labels).toEqual(response.listinfo.labels);
expect(updateListInfoRequest.comments).toEqual(response.listinfo.comments);

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

testHostUpdateListComponent.listInfoForm.submitData();
expect(listsEndpointSpy.admin.listsEndpoint.updateListInfo).toHaveBeenCalledTimes(1);
expect(listsEndpointSpy.admin.listsEndpoint.updateListInfo).toHaveBeenCalledWith(updateListInfoRequest);
});
});

it('should create', () => {
expect(component).toBeTruthy();
describe('create new list', () => {
beforeEach(() => {
testHostCreateListFixture = TestBed.createComponent(TestHostCreateListComponent);
testHostCreateListComponent = testHostCreateListFixture.componentInstance;
testHostCreateListFixture.detectChanges();
expect(testHostCreateListComponent).toBeTruthy();

rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostCreateListFixture);
});

it('should instantiate empty arrays for labels and comments', () => {
expect(testHostCreateListComponent.listInfoForm.labels).toEqual([]);
expect(testHostCreateListComponent.listInfoForm.comments).toEqual([]);
});

it('should display "Create" as the submit button text and be disabled as long as no labels are provided', async () => {
const submitButton = await rootLoader.getHarness(MatButtonHarness.with({ selector: '.list-submit' }));

expect(await submitButton.getText()).toEqual('Create');

expect(await submitButton.isDisabled()).toBeTruthy();

testHostCreateListComponent.listInfoForm.handleData([{ 'value': 'My new list', 'language': 'en' }], 'labels');

expect(await submitButton.isDisabled()).toBeFalsy();

testHostCreateListComponent.listInfoForm.handleData([], 'labels');

expect(await submitButton.isDisabled()).toBeTruthy();
});

it('should create a new list', () => {
const listsEndpointSpy = TestBed.inject(DspApiConnectionToken);

testHostCreateListComponent.listInfoForm.handleData([{ 'value': 'My new list', 'language': 'en' }], 'labels');
testHostCreateListComponent.listInfoForm.handleData([{ 'value': 'My new list comment', 'language': 'en' }], 'comments');

const createListRequest: CreateListRequest = new CreateListRequest();
createListRequest.projectIri = testHostCreateListComponent.listInfoForm.projectIri;
createListRequest.labels = testHostCreateListComponent.listInfoForm.labels;
createListRequest.comments = testHostCreateListComponent.listInfoForm.comments;

(listsEndpointSpy.admin.listsEndpoint as jasmine.SpyObj<ListsEndpointAdmin>).createList.and.callFake(
() => {
const response = new ListResponse();
response.list.children = [];
response.list.listinfo.labels = [{ 'value': 'My new list', 'language': 'en' }];
response.list.listinfo.comments = [{ 'value': 'My new list comment', 'language': 'en' }];

expect(createListRequest.labels).toEqual(response.list.listinfo.labels);
expect(createListRequest.comments).toEqual(response.list.listinfo.comments);

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

testHostCreateListComponent.listInfoForm.submitData();
expect(listsEndpointSpy.admin.listsEndpoint.createList).toHaveBeenCalledTimes(1);
expect(listsEndpointSpy.admin.listsEndpoint.createList).toHaveBeenCalledWith(createListRequest);
});
});

});

0 comments on commit 8824682

Please sign in to comment.