From c1204af55db728a9586d84b36019d17701bc6641 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sat, 2 Jul 2022 13:38:52 +0200 Subject: [PATCH 001/122] refactor(edition): refactor TableComponent --- .../shared/table/models/table-data.model.ts | 6 +- .../table-pagination.component.ts | 2 +- src/app/shared/table/table.component.html | 48 +++++++------ src/app/shared/table/table.component.scss | 3 + src/app/shared/table/table.component.ts | 67 +++++++++++-------- 5 files changed, 72 insertions(+), 54 deletions(-) diff --git a/src/app/shared/table/models/table-data.model.ts b/src/app/shared/table/models/table-data.model.ts index 2bd7020541..916de23922 100644 --- a/src/app/shared/table/models/table-data.model.ts +++ b/src/app/shared/table/models/table-data.model.ts @@ -36,9 +36,9 @@ export class TableData { * @param {any[]} rows The given table rows. */ constructor(header: string[], rows: any[]) { - this.header = header || ['']; - this.totalRows$ = observableOf(rows) || observableOf([]); + this.header = header || []; + this.totalRows$ = rows ? observableOf(rows) : observableOf([]); + this.paginatedRows$ = rows ? observableOf(rows) : observableOf([]); this.filteredRows = rows || []; - this.paginatedRows$ = observableOf(rows) || observableOf([]); } } diff --git a/src/app/shared/table/table-pagination/table-pagination.component.ts b/src/app/shared/table/table-pagination/table-pagination.component.ts index 3d0cf958f4..7684b51009 100644 --- a/src/app/shared/table/table-pagination/table-pagination.component.ts +++ b/src/app/shared/table/table-pagination/table-pagination.component.ts @@ -30,7 +30,7 @@ export class TablePaginationComponent implements OnInit { page: number; /** - * Output variable: pageChangeRequest. + * Output variable: pageChange. * * It keeps an event emitter for a change of the page number. */ diff --git a/src/app/shared/table/table.component.html b/src/app/shared/table/table.component.html index 84fccc01c3..942cb6180a 100644 --- a/src/app/shared/table/table.component.html +++ b/src/app/shared/table/table.component.html @@ -1,8 +1,8 @@ -
+
-
+
- + +
@@ -90,14 +93,17 @@
- + +
diff --git a/src/app/shared/table/table.component.scss b/src/app/shared/table/table.component.scss index e69de29bb2..645f600256 100644 --- a/src/app/shared/table/table.component.scss +++ b/src/app/shared/table/table.component.scss @@ -0,0 +1,3 @@ +.awg-pagesize-dropdown-button { + border: 1px solid #ced4da; +} diff --git a/src/app/shared/table/table.component.ts b/src/app/shared/table/table.component.ts index 09529f4240..0ca4283832 100644 --- a/src/app/shared/table/table.component.ts +++ b/src/app/shared/table/table.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -17,6 +17,7 @@ import { TablePaginatorOptions, TableData, TableOptions, TableRows } from './mod selector: 'awg-table', templateUrl: './table.component.html', styleUrls: ['./table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableComponent implements OnInit { /** @@ -114,7 +115,34 @@ export class TableComponent implements OnInit { * when initializing the component. */ ngOnInit(): void { - this._initTable(); + this.initTable(); + } + + /** + * Public method: initTable. + * + * It inits all the data needed for the table. + * + * @returns {void} Inits the table data. + */ + initTable(): void { + if (!this.headerInputData || !this.rowInputData) { + this.tableData = new TableData([], []); + } else { + this.tableData = new TableData(this.headerInputData, this.rowInputData); + } + + this.paginatorOptions = new TablePaginatorOptions( + 1, + 10, + [5, 10, 25, 50, 100, 200], + this.rowInputData?.length || 0 + ); + this.searchFilter = ''; + + this.onSort(this.tableData.header[0]); + + this.onPageSizeChange(this.searchFilter); } /** @@ -122,13 +150,20 @@ export class TableComponent implements OnInit { * * It emits the new start position of the Paginator * from a given page number to the {@link pageChangeRequest}. + * + * @param {string} searchFilter The given search filter. + * @param {number} selectedPageSizeOption The selected page size option. + * * @returns {void} Emits the new start position. */ - onPageSizeChange(searchFilter: string): void { + onPageSizeChange(searchFilter: string, selectedPageSizeOption?: number): void { if (!this.tableData || !this.headerInputData || !this.rowInputData) { return; } + if (selectedPageSizeOption) { + this.paginatorOptions.selectedPageSize = selectedPageSizeOption; + } this.tableData.paginatedRows$ = this._paginateRows(searchFilter); } @@ -193,32 +228,6 @@ export class TableComponent implements OnInit { this.clickedTableRowRequest.emit(e); } - /** - * Private method: _initTable. - * - * It inits all the data needed for the table. - * - * @returns {void} Inits the table data. - */ - private _initTable(): void { - if (!this.headerInputData || !this.rowInputData) { - this.tableData = new TableData([], []); - } else { - this.tableData = new TableData(this.headerInputData, this.rowInputData); - } - this.paginatorOptions = new TablePaginatorOptions( - 1, - 10, - [5, 10, 25, 50, 100, 200], - this.rowInputData?.length || 0 - ); - this.searchFilter = ''; - - this.onSort(this.tableData.header[0]); - - this.onPageSizeChange(this.searchFilter); - } - /** * Private method: _paginateRows. * From c4251192f2bac2461d9f6b76d3899727249f9c6c Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sat, 2 Jul 2022 13:45:55 +0200 Subject: [PATCH 002/122] test(edition): add tests for TableComponent --- src/app/shared/table/table.component.spec.ts | 940 ++++++++++++++++++- 1 file changed, 931 insertions(+), 9 deletions(-) diff --git a/src/app/shared/table/table.component.spec.ts b/src/app/shared/table/table.component.spec.ts index 9a74453044..533c0bce71 100644 --- a/src/app/shared/table/table.component.spec.ts +++ b/src/app/shared/table/table.component.spec.ts @@ -1,12 +1,40 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; +import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; +import { FormsModule } from '@angular/forms'; -import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; +import Spy = jasmine.Spy; -import { getAndExpectDebugElementByCss, getAndExpectDebugElementByDirective } from '@testing/expect-helper'; +import { EMPTY, lastValueFrom } from 'rxjs'; +import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testing'; +import { faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'; +import { NgbHighlight, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; +import { OrderModule } from 'ngx-order-pipe'; + +import { BUTTON_CLICK_EVENTS, clickAndAwaitChanges } from '@testing/click-helper'; +import { detectChangesOnPush } from '@testing/detect-changes-on-push-helper'; +import { + expectSpyCall, + getAndExpectDebugElementByCss, + getAndExpectDebugElementByDirective, +} from '@testing/expect-helper'; + +import { TableData, TableOptions, TablePaginatorOptions } from './models'; import { TableComponent } from './table.component'; +// Mock components +@Component({ selector: 'awg-table-pagination', template: '' }) +class TablePaginationStubComponent { + @Input() + collectionSize: number; + @Input() + page: number; + @Output() + pageChange: EventEmitter = new EventEmitter(); + @Output() + pageChangeRequest: EventEmitter = new EventEmitter(); +} + @Component({ selector: 'awg-twelve-tone-spinner', template: '' }) class TwelveToneSpinnerStubComponent {} @@ -15,18 +43,93 @@ describe('TableComponent', () => { let fixture: ComponentFixture; let compDe: DebugElement; + let initSpy: Spy; + let onSortSpy: Spy; + let onPageSizeChangeSpy: Spy; + let paginateRowsSpy: Spy; + let onTableValueClickSpy: Spy; + let onTableRowClickSpy: Spy; + let clickedTableValueRequestSpy: Spy; + let clickedTableRowRequestSpy: Spy; + + let expectedTableTitle: string; + let expectedHeaderInputData: any; + let expectedRowInputData: any; + let expectedTableData: TableData; + let expectedTableOptions: TableOptions; + let expectedPaginatorOptions: TablePaginatorOptions; + beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [NgbPaginationModule], - declarations: [TableComponent, TwelveToneSpinnerStubComponent], + imports: [FontAwesomeTestingModule, FormsModule, NgbPaginationModule, OrderModule], + declarations: [TableComponent, TablePaginationStubComponent, NgbHighlight, TwelveToneSpinnerStubComponent], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TableComponent); - component = fixture.componentInstance; compDe = fixture.debugElement; + + // Test data + expectedTableTitle = 'Table title'; + expectedHeaderInputData = ['column1', 'column2', 'column3']; + expectedRowInputData = [ + { + column1: { value: 'Value Column 1 Row 1', label: 'ValueColumn1Row1', type: 'uri' }, + column2: { value: 'Value Column 2 Row 1', label: 'ValueColumn2Row1', type: 'uri' }, + column3: { value: 'Value Column 3 Row 1', label: 'ValueColumn3Row1', type: 'uri' }, + }, + { + column1: { value: 'Value Column 1 Row 2', label: 'ValueColumn1Row2', type: 'uri' }, + column2: { value: 'Value Column 2 Row 2', label: 'ValueColumn2Row2', type: 'uri' }, + column3: { value: 'Value Column 3 Row 2', label: 'ValueColumn3Row2', type: 'uri' }, + }, + { + column1: { value: 'Value Column 1 Row 3', label: 'ValueColumn1Row3', type: 'uri' }, + column2: { value: 'Value Column 2 Row 3', label: 'ValueColumn2Row3', type: 'uri' }, + column3: { value: 'Value Column 3 Row 3', label: 'ValueColumn3Row3', type: 'uri' }, + }, + { + column1: { value: 'Value Column 1 Row 4', label: 'ValueColumn1Row4', type: 'uri' }, + column2: { value: 'Value Column 2 Row 4', label: 'ValueColumn2Row4', type: 'uri' }, + column3: { value: 'Value Column 3 Row 4', label: 'ValueColumn3Row4', type: 'uri' }, + }, + { + column1: { value: 'Value Column 1 Row 5', label: 'ValueColumn1Row5', type: 'uri' }, + column2: { value: 'Value Column 2 Row 5', label: 'ValueColumn2Row5', type: 'uri' }, + column3: { value: 'Value Column 3 Row 5', label: 'ValueColumn3Row5', type: 'uri' }, + }, + { + column1: { value: 'Value Column 1 Row 6', label: 'ValueColumn1Row6', type: 'uri' }, + column2: { value: 'Value Column 2 Row 6', label: 'ValueColumn2Row6', type: 'uri' }, + column3: { value: 'Value Column 3 Row 6', label: 'ValueColumn3Row6', type: 'uri' }, + }, + ]; + expectedTableData = new TableData(expectedHeaderInputData, expectedRowInputData); + expectedTableOptions = { + selectedKey: '', + sortKey: '', + sortIcon: component.faSortDown, + reverse: false, + isCaseInsensitive: false, + }; + expectedPaginatorOptions = new TablePaginatorOptions( + 1, + 10, + [5, 10, 25, 50, 100, 200], + expectedRowInputData.length + ); + + // Spies on methods + initSpy = spyOn(component, 'initTable').and.callThrough(); + onSortSpy = spyOn(component, 'onSort').and.callThrough(); + onPageSizeChangeSpy = spyOn(component, 'onPageSizeChange').and.callThrough(); + paginateRowsSpy = spyOn(component as any, '_paginateRows').and.callThrough(); + onTableValueClickSpy = spyOn(component, 'onTableValueClick').and.callThrough(); + onTableRowClickSpy = spyOn(component, 'onTableRowClick').and.callThrough(); + clickedTableValueRequestSpy = spyOn(component.clickedTableValueRequest, 'emit').and.callThrough(); + clickedTableRowRequestSpy = spyOn(component.clickedTableRowRequest, 'emit').and.callThrough(); }); it('should create', () => { @@ -34,10 +137,57 @@ describe('TableComponent', () => { }); describe('BEFORE initial data binding', () => { - it('should not have paginatorOptions', () => { + it('should not have tableTitle yet', () => { + expect(component.tableTitle).toBeUndefined(); + }); + + it('should not have headerInputData yet', () => { + expect(component.headerInputData).toBeUndefined(); + }); + + it('should not have rowInputData yet', () => { + expect(component.rowInputData).toBeUndefined(); + }); + + it('should not have paginatorOptions yet', () => { expect(component.paginatorOptions).toBeUndefined(); }); + it('should not have searchFilter yet', () => { + expect(component.searchFilter).toBeUndefined(); + }); + + it('should not have tableData yet', () => { + expect(component.tableData).toBeUndefined(); + }); + + it('should have faSortUp and faSortDown icons', () => { + expect(component.faSortUp).toBeDefined(); + expect(component.faSortUp).withContext(`should be faSortUp`).toBe(faSortUp); + + expect(component.faSortDown).toBeDefined(); + expect(component.faSortDown).withContext(`should be faSortDown`).toBe(faSortDown); + }); + + it('should have tableOptions', () => { + expect(component.tableOptions).toBeDefined(); + expect(component.tableOptions) + .withContext(`should equal ${expectedTableOptions}`) + .toEqual(expectedTableOptions); + }); + + it('should not have called initTable()', () => { + expectSpyCall(initSpy, 0); + }); + + it('should not have called onSort()', () => { + expectSpyCall(onSortSpy, 0); + }); + + it('should not have called onPageSizeChange()', () => { + expectSpyCall(onPageSizeChangeSpy, 0); + }); + describe('VIEW', () => { it('... should contain no form yet', () => { getAndExpectDebugElementByCss(compDe, 'form', 0, 0); @@ -48,7 +198,7 @@ describe('TableComponent', () => { }); it('... should contain no table yet', () => { - getAndExpectDebugElementByCss(compDe, 'table.table', 0, 0); + getAndExpectDebugElementByCss(compDe, 'table', 0, 0); }); it('... should not display TwelveToneSpinnerComponent (stubbed)', () => { @@ -56,4 +206,776 @@ describe('TableComponent', () => { }); }); }); + + describe('AFTER initial data binding', () => { + beforeEach(() => { + // Simulate the parent setting the input properties + component.tableTitle = 'Table title'; + component.headerInputData = expectedHeaderInputData; + component.rowInputData = expectedRowInputData; + + // Trigger initial data binding + fixture.detectChanges(); + }); + + it('should have tableTitle', () => { + expect(component.tableTitle).toBeDefined(); + expect(component.tableTitle).withContext(`should be ${expectedTableTitle}`).toBe(expectedTableTitle); + }); + + it('should have headerInputData', () => { + expect(component.headerInputData).toBeDefined(); + expect(component.headerInputData) + .withContext(`should equal ${expectedHeaderInputData}`) + .toEqual(expectedHeaderInputData); + }); + + it('should have rowInputData', () => { + expect(component.rowInputData).toBeDefined(); + expect(component.rowInputData) + .withContext(`should equal ${expectedRowInputData}`) + .toEqual(expectedRowInputData); + }); + + describe('#initTable()', () => { + it('should have an initTable() method', () => { + expect(component.initTable).toBeDefined(); + }); + + it('should have been called', () => { + expectSpyCall(initSpy, 1); + }); + + describe('should set tableData', () => { + it('... with headerInputData and rowInputData', waitForAsync(() => { + expect(component.tableData).toBeDefined(); + + expect(component.tableData.header).toBeDefined(); + expect(component.tableData.header) + .withContext(`should equal ${expectedHeaderInputData}`) + .toEqual(expectedHeaderInputData); + + expect(component.tableData.filteredRows).toBeDefined(); + expect(component.tableData.filteredRows) + .withContext(`should equal ${expectedRowInputData}`) + .toEqual(expectedRowInputData); + + expect(component.tableData.paginatedRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to ${expectedRowInputData}`) + .toBeResolvedTo(expectedRowInputData); + + expect(component.tableData.totalRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.totalRows$)) + .withContext(`should be resolved to ${expectedRowInputData}`) + .toBeResolvedTo(expectedRowInputData); + })); + + describe('... to empty object', () => { + it('... if headerInputData is not given', waitForAsync(() => { + component.headerInputData = undefined; + component.rowInputData = expectedRowInputData; + + component.initTable(); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + + expect(component.tableData.header).toBeDefined(); + expect(component.tableData.header).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.filteredRows).toBeDefined(); + expect(component.tableData.filteredRows).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.paginatedRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + + expect(component.tableData.totalRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.totalRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + })); + + it('... if rowInputData is not given', waitForAsync(() => { + component.headerInputData = expectedHeaderInputData; + component.rowInputData = undefined; + + component.initTable(); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + + expect(component.tableData.header).toBeDefined(); + expect(component.tableData.header).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.filteredRows).toBeDefined(); + expect(component.tableData.filteredRows).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.paginatedRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + + expect(component.tableData.totalRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.totalRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + })); + + it('... if both headerInputData and rowInputData are not given', waitForAsync(() => { + component.headerInputData = undefined; + component.rowInputData = undefined; + + component.initTable(); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + + expect(component.tableData.header).toBeDefined(); + expect(component.tableData.header).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.filteredRows).toBeDefined(); + expect(component.tableData.filteredRows).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.paginatedRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + + expect(component.tableData.totalRows$).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.totalRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + })); + }); + }); + + it('should set paginatorOptions', () => { + expect(component.paginatorOptions).toBeDefined(); + expect(component.paginatorOptions) + .withContext(`should equal ${expectedPaginatorOptions}`) + .toEqual(expectedPaginatorOptions); + }); + + it('should set paginatorOptions.collectionSize to tableData.rowInputData.length', () => { + expect(component.paginatorOptions.collectionSize).toBeDefined(); + expect(component.paginatorOptions.collectionSize) + .withContext(`should equal ${expectedPaginatorOptions.collectionSize}`) + .toEqual(expectedPaginatorOptions.collectionSize); + }); + + it('should set paginatorOptions.collectionSize to 0 if tableData.rowInputData is not given', () => { + component.rowInputData = undefined; + component.initTable(); + fixture.detectChanges(); + + expect(component.paginatorOptions.collectionSize).toBeDefined(); + expect(component.paginatorOptions.collectionSize).withContext(`should equal 0`).toEqual(0); + }); + + it('should trigger onSort()', () => { + expectSpyCall(onSortSpy, 1); + }); + + it('should trigger onPageSizeChange()', () => { + expectSpyCall(onPageSizeChangeSpy, 1); + }); + }); + + describe('#onPageSizeChange()', () => { + it('should have an onPageSizeChange() method', () => { + expect(component.onPageSizeChange).toBeDefined(); + }); + + it('should have been called', () => { + expectSpyCall(onPageSizeChangeSpy, 1); + }); + + it('should trigger on change of searchFilter in input', async () => { + await fixture.whenStable(); // Needed to wait for the ngModelto be initialized, cf. https://github.com/angular/angular/issues/22606#issuecomment-514760743 + + const expectedSearchFilter = 'test'; + const otherSearchFilter = 'other'; + + const inputDe = getAndExpectDebugElementByCss(compDe, 'input[name="searchFilter"]', 1, 1); + const inputEl = inputDe[0].nativeElement; + + inputEl.value = expectedSearchFilter; + inputEl.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + // First call happens on ngOnInit() + expectSpyCall(onPageSizeChangeSpy, 2, expectedSearchFilter); + expect(component.searchFilter).toBeDefined(); + expect(component.searchFilter) + .withContext(`should equal ${expectedSearchFilter}`) + .toBe(expectedSearchFilter); + + inputEl.value = otherSearchFilter; + inputEl.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + expectSpyCall(onPageSizeChangeSpy, 3, otherSearchFilter); + expect(component.searchFilter).toBeDefined(); + expect(component.searchFilter).withContext(`should equal ${otherSearchFilter}`).toBe(otherSearchFilter); + }); + + it('should trigger on change of selectedPageSize in upper dropdown menu', fakeAsync(() => { + const expectedItemNumber = component.paginatorOptions.pageSizeOptions.length; + + const divDes = getAndExpectDebugElementByCss(compDe, 'div.awg-pagination', 2, 2); + const dropdownDes1 = getAndExpectDebugElementByCss( + divDes[0], + 'div.d-inline-block > div.dropdown-menu', + 1, + 1 + ); + const buttonDes1 = getAndExpectDebugElementByCss( + dropdownDes1[0], + 'button.dropdown-item', + expectedItemNumber, + expectedItemNumber + ); + + // Click on first button + clickAndAwaitChanges(buttonDes1[0], fixture); + + // First call happens on ngOnInit() + expectSpyCall(onPageSizeChangeSpy, 2, ['', component.paginatorOptions.pageSizeOptions[0]]); + + // Click on second button + clickAndAwaitChanges(buttonDes1[1], fixture); + + expectSpyCall(onPageSizeChangeSpy, 3, ['', component.paginatorOptions.pageSizeOptions[1]]); + + // Click on third button + clickAndAwaitChanges(buttonDes1[2], fixture); + + expectSpyCall(onPageSizeChangeSpy, 4, ['', component.paginatorOptions.pageSizeOptions[2]]); + + // Click on fourth button + clickAndAwaitChanges(buttonDes1[3], fixture); + + expectSpyCall(onPageSizeChangeSpy, 5, ['', component.paginatorOptions.pageSizeOptions[3]]); + + // Click on fifth button + clickAndAwaitChanges(buttonDes1[4], fixture); + + expectSpyCall(onPageSizeChangeSpy, 6, ['', component.paginatorOptions.pageSizeOptions[4]]); + + // Click on sixth button + clickAndAwaitChanges(buttonDes1[5], fixture); + + expectSpyCall(onPageSizeChangeSpy, 7, ['', component.paginatorOptions.pageSizeOptions[5]]); + })); + + it('should trigger on change of selectedPageSize in lower dropdown menu', fakeAsync(() => { + const expectedItemNumber = component.paginatorOptions.pageSizeOptions.length; + + const divDes = getAndExpectDebugElementByCss(compDe, 'div.awg-pagination', 2, 2); + const dropdownDes2 = getAndExpectDebugElementByCss( + divDes[1], + 'div.d-inline-block > div.dropdown-menu', + 1, + 1 + ); + const buttonDes2 = getAndExpectDebugElementByCss( + dropdownDes2[0], + 'button.dropdown-item', + expectedItemNumber, + expectedItemNumber + ); + + // Click on first button + clickAndAwaitChanges(buttonDes2[0], fixture); + + // First call happens on ngOnInit() + expectSpyCall(onPageSizeChangeSpy, 2, ['', component.paginatorOptions.pageSizeOptions[0]]); + + // Click on second button + clickAndAwaitChanges(buttonDes2[1], fixture); + + expectSpyCall(onPageSizeChangeSpy, 3, ['', component.paginatorOptions.pageSizeOptions[1]]); + + // Click on third button + clickAndAwaitChanges(buttonDes2[2], fixture); + + expectSpyCall(onPageSizeChangeSpy, 4, ['', component.paginatorOptions.pageSizeOptions[2]]); + + // Click on fourth button + clickAndAwaitChanges(buttonDes2[3], fixture); + + expectSpyCall(onPageSizeChangeSpy, 5, ['', component.paginatorOptions.pageSizeOptions[3]]); + + // Click on fifth button + clickAndAwaitChanges(buttonDes2[4], fixture); + + expectSpyCall(onPageSizeChangeSpy, 6, ['', component.paginatorOptions.pageSizeOptions[4]]); + + // Click on sixth button + clickAndAwaitChanges(buttonDes2[5], fixture); + + expectSpyCall(onPageSizeChangeSpy, 7, ['', component.paginatorOptions.pageSizeOptions[5]]); + })); + + it('should trigger on event from both TablePaginationComponents', fakeAsync(() => { + component.searchFilter = 'test'; + + const tablePaginationDes = getAndExpectDebugElementByDirective( + compDe, + TablePaginationStubComponent, + 2, + 2 + ); + + const tablePaginationCmps = tablePaginationDes.map( + de => de.injector.get(TablePaginationStubComponent) as TablePaginationStubComponent + ); + + tablePaginationCmps[0].pageChangeRequest.emit(10); + + expectSpyCall(onPageSizeChangeSpy, 2, 'test'); + + tablePaginationCmps[1].pageChangeRequest.emit(250); + + expectSpyCall(onPageSizeChangeSpy, 3, 'test'); + })); + + it('should call paginateRows() with given searchfilter', () => { + component.onPageSizeChange('test'); + fixture.detectChanges(); + + expectSpyCall(paginateRowsSpy, 2, 'test'); + }); + + describe('should filter tableData', () => { + it('... by matching searchFilter', async () => { + const searchFilter = 'ValueColumn1Row1'; + await component.onPageSizeChange(searchFilter); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + expect(component.tableData.filteredRows.length).withContext(`should be 1`).toBe(1); + expect(component.tableData.filteredRows) + .withContext(`should equal ${[expectedRowInputData.at(0)]}`) + .toEqual([expectedRowInputData.at(0)]); + }); + + it('... by non-matching searchFilter (empty array)', async () => { + const searchFilter = 'test'; + await component.onPageSizeChange(searchFilter); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + expect(component.tableData.filteredRows.length).withContext(`should be 0`).toBe(0); + expect(component.tableData.filteredRows).withContext(`should equal empty array`).toEqual([]); + }); + + it('... if a rowEntry is null or undefined', async () => { + const expectedFilteredRows = expectedRowInputData.slice(0, 4); + + expectedRowInputData[expectedRowInputData.length - 2] = { + column1: null, + column2: null, + column3: null, + }; + expectedRowInputData[expectedRowInputData.length - 1] = { + column1: undefined, + column2: undefined, + column3: undefined, + }; + component.tableData = new TableData(expectedHeaderInputData, expectedRowInputData); + + const searchFilter = ''; + await component.onPageSizeChange(searchFilter); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + expect(component.tableData.filteredRows.length) + .withContext(`should be ${expectedFilteredRows.length}`) + .toBe(expectedFilteredRows.length); + expect(component.tableData.filteredRows) + .withContext(`should equal ${expectedFilteredRows}`) + .toEqual(expectedFilteredRows); + }); + + it('... if table data is empty (empty array)', async () => { + component.tableData = new TableData(null, null); + + await component.onPageSizeChange(''); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + + expect(component.tableData.header).toBeDefined(); + expect(component.tableData.header).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.filteredRows).toBeDefined(); + expect(component.tableData.filteredRows).withContext(`should equal []`).toEqual([]); + + expect(component.tableData.paginatedRows$).toBeDefined(); + await expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + + expect(component.tableData.totalRows$).toBeDefined(); + await expectAsync(lastValueFrom(component.tableData.totalRows$)) + .withContext(`should be resolved to []`) + .toBeResolvedTo([]); + }); + }); + + it('should slice tableData by range of paginatorOptions.selectedPageSize', waitForAsync(() => { + const expectedPageSize = component.paginatorOptions.pageSizeOptions[0]; + const expectedPaginatedRows = expectedRowInputData.slice(0, expectedPageSize); + + component.paginatorOptions.selectedPageSize = expectedPageSize; + component.onPageSizeChange('', expectedPageSize); + fixture.detectChanges(); + + expect(component.tableData).toBeDefined(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)).toBeResolved(); + expectAsync(lastValueFrom(component.tableData.paginatedRows$)) + .withContext(`should be resolved to ${expectedPaginatedRows}`) + .toBeResolvedTo(expectedPaginatedRows); + })); + + describe('should not do anything', () => { + it('... if tableData is undefined', () => { + component.tableData = undefined; + component.onPageSizeChange('test'); + fixture.detectChanges(); + + expectSpyCall(paginateRowsSpy, 1); + }); + + it('... if headerInputData is undefined', () => { + component.headerInputData = undefined; + component.onPageSizeChange('test'); + fixture.detectChanges(); + + expectSpyCall(paginateRowsSpy, 1); + }); + + it('... if rowInputData is undefined', () => { + component.rowInputData = undefined; + component.onPageSizeChange('test'); + fixture.detectChanges(); + + expectSpyCall(paginateRowsSpy, 1); + }); + }); + }); + + describe('#onSort()', () => { + it('should have an onSort() method', () => { + expect(component.onSort).toBeDefined(); + }); + + it('should have been called', () => { + expectSpyCall(onSortSpy, 1); + }); + + it('... should trigger on click on table header', fakeAsync(() => { + const headerDe = getAndExpectDebugElementByCss(compDe, 'table > thead > tr > th', 3, 3); + + // Click on first header + clickAndAwaitChanges(headerDe[0], fixture); + + // First call happens on ngOnInit() + expectSpyCall(onSortSpy, 2, expectedHeaderInputData[0]); + + // Click on second header + clickAndAwaitChanges(headerDe[1], fixture); + + expectSpyCall(onSortSpy, 3, expectedHeaderInputData[1]); + + // Click on third header + clickAndAwaitChanges(headerDe[2], fixture); + + expectSpyCall(onSortSpy, 4, expectedHeaderInputData[2]); + })); + + it('should set tableOptions.selectedKey to the given key', () => { + expect(component.tableOptions.selectedKey).toBeDefined(); + expect(component.tableOptions.selectedKey) + .withContext(`should equal ${expectedHeaderInputData[0]}`) + .toBe(expectedHeaderInputData[0]); + + component.onSort('key'); + + expect(component.tableOptions.selectedKey).toBe('key'); + }); + + it('should set tableOptions.sortKey', () => { + expect(component.tableOptions.sortKey).toBeDefined(); + expect(component.tableOptions.sortKey) + .withContext(`should equal ${expectedHeaderInputData[0] + '.label'}`) + .toBe(expectedHeaderInputData[0] + '.label'); + + component.onSort('key'); + + expect(component.tableOptions.sortKey).toBe('key.label'); + }); + + describe('should set tableOptions.reverse', () => { + it('... to false by default', () => { + expect(component.tableOptions.reverse).toBe(false); + }); + + it('... to false when called with different keys', () => { + expect(component.tableOptions.reverse).toBe(false); + + component.onSort('key'); + + expect(component.tableOptions.reverse).toBe(false); + + component.onSort('key2'); + + expect(component.tableOptions.reverse).toBe(false); + }); + + it('... toggling false/true if key equals selected key', () => { + component.onSort('key'); + + expect(component.tableOptions.reverse).toBe(false); + + component.onSort('key'); + + expect(component.tableOptions.reverse).toBe(true); + + component.onSort('key'); + + expect(component.tableOptions.reverse).toBe(false); + }); + }); + + describe('should set tableOptions.sortIcon', () => { + it('... to faSortDown by default', () => { + component.onSort('key'); + + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon) + .withContext(`should equal ${faSortDown}`) + .toEqual(faSortDown); + }); + + it('... to faSortUp if tableOptions.reverse is true', () => { + component.tableOptions.reverse = true; + component.onSort('key'); + fixture.detectChanges(); + + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon).withContext(`should equal ${faSortUp}`).toEqual(faSortUp); + }); + + it('... toggling faSortDown/faSortUp if key equals selected key', () => { + component.onSort('key'); + + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon) + .withContext(`should equal ${faSortDown}`) + .toEqual(faSortDown); + + component.onSort('key'); + + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon).withContext(`should equal ${faSortUp}`).toEqual(faSortUp); + + component.onSort('key'); + + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon) + .withContext(`should equal ${faSortDown}`) + .toEqual(faSortDown); + }); + }); + + describe('should set tableOptions.reverse ', () => { + it('... to false by default', () => { + expect(component.tableOptions.reverse).toBeDefined(); + expect(component.tableOptions.reverse).toBe(false); + }); + it('... toggling true/false if tableOptions.selectedKey is the same as given key', () => { + component.tableOptions.selectedKey = expectedHeaderInputData[0]; + + component.onSort(expectedHeaderInputData[0]); + + expect(component.tableOptions.reverse).toBe(true); + + component.onSort(expectedHeaderInputData[0]); + + expect(component.tableOptions.reverse).toBe(false); + }); + }); + + it('should not do anything if no key is given', () => { + component.onSort(undefined); + fixture.detectChanges(); + + expect(component.tableOptions.selectedKey).toBeDefined(); + expect(component.tableOptions.selectedKey).toBe(expectedHeaderInputData[0]); + expect(component.tableOptions.sortKey).toBeDefined(); + expect(component.tableOptions.sortKey).toBe(expectedHeaderInputData[0] + '.label'); + expect(component.tableOptions.sortIcon).toBeDefined(); + expect(component.tableOptions.sortIcon).toEqual(faSortDown); + expect(component.tableOptions.reverse).toBeDefined(); + expect(component.tableOptions.reverse).toBe(false); + expect(component.tableOptions.isCaseInsensitive).toBeDefined(); + expect(component.tableOptions.isCaseInsensitive).toBe(false); + }); + }); + + describe('#onTableValueClick()', () => { + it('should have an onTableValueClick() method', () => { + expect(component.onTableValueClick).toBeDefined(); + }); + + it('... should not have been called', () => { + expect(component.onTableValueClick).not.toHaveBeenCalled(); + }); + + it('... should trigger on click if a row value has type===uri', fakeAsync(() => { + const rowDes = getAndExpectDebugElementByCss( + compDe, + 'table > tbody > tr', + expectedRowInputData.length, + expectedRowInputData.length + ); + + // Find anchors in rows with type===uri (first) + const anchorDes = getAndExpectDebugElementByCss(rowDes[0], 'a', 3, 3); + + anchorDes.forEach((anchorDe, index) => { + clickAndAwaitChanges(anchorDe, fixture); + expectSpyCall( + onTableValueClickSpy, + index + 1, + expectedRowInputData[0]['column' + (index + 1)].value + ); + }); + })); + + describe('... should not emit anything if ', () => { + it('... event is undefined', () => { + component.onTableValueClick(undefined); + + expectSpyCall(clickedTableValueRequestSpy, 0); + }); + + it('... event is null', () => { + component.onTableValueClick(undefined); + + expectSpyCall(clickedTableValueRequestSpy, 0, null); + }); + it('... event is empty string', () => { + component.onTableValueClick(''); + + expectSpyCall(clickedTableValueRequestSpy, 0, ''); + }); + }); + + it('... should emit a given event', () => { + const expectedEvent = 'test'; + component.onTableValueClick(expectedEvent); + + expectSpyCall(clickedTableValueRequestSpy, 1, expectedEvent); + }); + }); + + describe('#onTableRowClick()', () => { + it('should have an onTableRowClick() method', () => { + expect(component.onTableRowClick).toBeDefined(); + }); + + it('... should not have been called', () => { + expect(component.onTableValueClick).not.toHaveBeenCalled(); + }); + + it('... should trigger on click on a row', fakeAsync(() => { + const rowDes = getAndExpectDebugElementByCss( + compDe, + 'table > tbody > tr', + expectedRowInputData.length, + expectedRowInputData.length + ); + + // Click through rows + rowDes.forEach((rowDe, index) => { + clickAndAwaitChanges(rowDe, fixture); + + expectSpyCall(onTableRowClickSpy, index + 1, BUTTON_CLICK_EVENTS.left); + }); + })); + + describe('... should not emit anything if ', () => { + it('... event is undefined', () => { + component.onTableRowClick(undefined); + + expectSpyCall(clickedTableRowRequestSpy, 0); + }); + + it('... event is null', () => { + component.onTableRowClick(null); + + expectSpyCall(clickedTableRowRequestSpy, 0, null); + }); + + it('... event is empty string', () => { + component.onTableRowClick(''); + + expectSpyCall(clickedTableRowRequestSpy, 0, ''); + }); + }); + + it('... should emit a given event', () => { + const expectedEvent = 'test'; + component.onTableRowClick(expectedEvent); + + expectSpyCall(clickedTableRowRequestSpy, 1, expectedEvent); + }); + }); + + describe('VIEW', () => { + it('should pass down paginatorOptions to pagination component', () => { + const tablePaginationDes = getAndExpectDebugElementByDirective( + compDe, + TablePaginationStubComponent, + 2, + 2 + ); + const tablePaginationCmps = tablePaginationDes.map( + de => de.injector.get(TablePaginationStubComponent) as TablePaginationStubComponent + ); + + expect(tablePaginationCmps.length).withContext('should have 2 pagination components').toBe(2); + + expect(tablePaginationCmps[0].collectionSize).toBeTruthy(); + expect(tablePaginationCmps[0].collectionSize) + .withContext(`should equal ${expectedRowInputData.length}`) + .toEqual(expectedRowInputData.length); + + expect(tablePaginationCmps[1].collectionSize).toBeTruthy(); + expect(tablePaginationCmps[1].collectionSize) + .withContext(`should equal ${expectedRowInputData.length}`) + .toEqual(expectedRowInputData.length); + + expect(tablePaginationCmps[0].page).toBeTruthy(); + expect(tablePaginationCmps[0].page).withContext(`should be 1`).toBe(1); + + expect(tablePaginationCmps[1].page).toBeTruthy(); + expect(tablePaginationCmps[1].page).withContext(`should be 1`).toBe(1); + }); + + it('... should display TwelveToneSpinnerComponent (stubbed) while loading (paginatedRows are not available)', () => { + // Mock empty observable + component.tableData.paginatedRows$ = EMPTY; + detectChangesOnPush(fixture); + + getAndExpectDebugElementByDirective(compDe, TwelveToneSpinnerStubComponent, 1, 1); + }); + }); + }); }); From 370930fc8be824bc6554e5e69cb847caee903096 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sat, 2 Jul 2022 13:49:05 +0200 Subject: [PATCH 003/122] test(edition): remove unused imports from test for EditionIntroComponent --- .../edition-intro/edition-intro.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-intro/edition-intro.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-intro/edition-intro.component.spec.ts index 7ecdc09388..812a8b6fcf 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-intro/edition-intro.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-intro/edition-intro.component.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; -import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; +import { Component, DebugElement } from '@angular/core'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; From d1f922a26412f18525ea703687fad33d6e70b658 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sat, 2 Jul 2022 13:50:05 +0200 Subject: [PATCH 004/122] test(search): adjust tests for DataViewComponent --- .../data-view/data-view.component.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/views/data-view/data-view.component.spec.ts b/src/app/views/data-view/data-view.component.spec.ts index 80461f2555..2489657f95 100644 --- a/src/app/views/data-view/data-view.component.spec.ts +++ b/src/app/views/data-view/data-view.component.spec.ts @@ -66,10 +66,10 @@ describe('DataViewComponent (DONE)', () => { describe('BEFORE initial data binding', () => { it('should have title and id', () => { expect(component.searchTitle).toBeDefined(); - expect(component.searchTitle).toBe(expectedTitle); + expect(component.searchTitle).withContext(`should be ${expectedTitle}`).toBe(expectedTitle); expect(component.searchId).toBeDefined(); - expect(component.searchId).toBe(expectedId); + expect(component.searchId).withContext(`should be ${expectedId}`).toBe(expectedId); }); describe('#routeToSidenav', () => { @@ -131,10 +131,10 @@ describe('DataViewComponent (DONE)', () => { const navArgs = navigationSpy.calls.first().args; const outletRoute = navArgs[0][0].outlets.side; - expect(navArgs).toBeDefined('should have navArgs'); - expect(navArgs[0]).toBeDefined('should have navCommand'); - expect(outletRoute).toBeDefined('should have outletRoute'); - expect(outletRoute).toBe(expectedRoute, `should be: ${expectedRoute}`); + expect(navArgs).toBeDefined(); + expect(navArgs[0]).toBeDefined(); + expect(outletRoute).toBeDefined(); + expect(outletRoute).withContext(`should be: ${expectedRoute}`).toBe(expectedRoute); expect(navigationSpy).toHaveBeenCalledWith(navArgs[0], navArgs[1]); }); @@ -144,9 +144,9 @@ describe('DataViewComponent (DONE)', () => { const navArgs = navigationSpy.calls.first().args; const navExtras = navArgs[1]; - expect(navExtras).toBeDefined('should have navExtras'); - expect(navExtras.preserveFragment).toBeDefined('should have preserveFragment extra'); - expect(navExtras.preserveFragment).toBe(true, 'should be `preserveFragment:true`'); + expect(navExtras).toBeDefined(); + expect(navExtras.preserveFragment).toBeDefined(); + expect(navExtras.preserveFragment).withContext('should be `preserveFragment:true`').toBe(true); expect(navigationSpy).toHaveBeenCalledWith(navArgs[0], navArgs[1]); }); @@ -158,10 +158,10 @@ describe('DataViewComponent (DONE)', () => { const headingCmp = headingDes[0].injector.get(HeadingStubComponent) as HeadingStubComponent; expect(headingCmp.title).toBeTruthy(); - expect(headingCmp.title).toBe(expectedTitle, `should have title: ${expectedTitle}`); + expect(headingCmp.title).withContext(`should have title: ${expectedTitle}`).toBe(expectedTitle); expect(headingCmp.id).toBeTruthy(); - expect(headingCmp.id).toBe(expectedId, `should have id: ${expectedId}`); + expect(headingCmp.id).withContext(`should have id: ${expectedId}`).toBe(expectedId); }); }); }); From ce101d41ef10a6a4f6f459006ab137dce59b89e0 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 13:11:02 +0200 Subject: [PATCH 005/122] feat(edition): add ToastMessage class to ToastService --- .../services/toast-service/toast.service.ts | 47 ++++++++++++++++++- .../graph-visualizer.component.ts | 25 +++++----- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/app/core/services/toast-service/toast.service.ts b/src/app/core/services/toast-service/toast.service.ts index 260aad00a8..db089a3eaf 100644 --- a/src/app/core/services/toast-service/toast.service.ts +++ b/src/app/core/services/toast-service/toast.service.ts @@ -1,5 +1,50 @@ import { Injectable, TemplateRef } from '@angular/core'; +/** + * The ToastMessage class. + * + * It is used in the context of the app + * to store and provide the data for a toast + * to be displayed with ngb-toast. + */ +export class ToastMessage { + /** + * Public variable: name. + * + * It keeps the name of the toast. + */ + name: string; + + /** + * Public variable: message. + * + * It keeps the message of the toast to be displayed. + */ + message: string; + + /** + * Public variable: duration. + * + * It keeps the duration of the toast. + */ + duration?: number; + + /** + * Constructor of the Toast class. + * + * It initializes the class with given values. + * + * @param {string} name The name of the toast. + * @param {string} message The message of the toast. + * @param {number} [duration] The optional duration of the toast. + */ + constructor(name: string, message: string, duration?: number) { + this.name = name; + this.message = message; + this.duration = duration || 3000; + } +} + /** * The Toast class. * @@ -23,7 +68,7 @@ export class Toast { * It initializes the class with given values. * * @param {string | TemplateRef<*>} textOrTpl The given text or template input. - * @param {[*]} options The optional options input. + * @param {*} [options] The optional options input. */ constructor(textOrTpl: string | TemplateRef, options?: any) { this.textOrTpl = textOrTpl; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts index ac92df9d12..6e949e0380 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts @@ -6,7 +6,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChil import { EMPTY, from, Observable } from 'rxjs'; -import { Toast, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastMessage, ToastService } from '@awg-core/services/toast-service'; import { GraphSparqlQuery, GraphRDFData } from '@awg-views/edition-view/models'; import { D3SimulationNode, QueryResult, Triple } from './models'; @@ -217,24 +217,23 @@ export class GraphVisualizerComponent implements OnInit { * * It shows a given error message for a given duration. * - * @param {string} name The given error name. - * @param {string} message The given error message. - * @param {number} [durationValue] The given optional duration in ms. + * @param {ToastMessage} toastMessage The given toast message. * * @returns {void} Shows the error message. */ - showErrorMessage(name: string, message: string, durationValue?: number): void { - if (!message) { + showErrorMessage(toastMessage: ToastMessage): void { + if (!toastMessage) { return; } - if (!durationValue) { - durationValue = 7000; - } - const toast = new Toast(message, { header: name, classname: 'bg-danger text-light', delay: durationValue }); + const toast = new Toast(toastMessage.message, { + header: toastMessage.name, + classname: 'bg-danger text-light', + delay: toastMessage.duration, + }); this.toastService.add(toast); - console.error(message, durationValue); + console.error(toastMessage.name, ':', toastMessage.message); } /** @@ -265,9 +264,9 @@ export class GraphVisualizerComponent implements OnInit { if (err.message && err.name) { if (err.message.indexOf('undefined') !== -1) { - this.showErrorMessage(err.name, 'The query did not return any results', 10000); + this.showErrorMessage(new ToastMessage(err.name, 'The query did not return any results', 10000)); } - this.showErrorMessage(err.name, err.message, 10000); + this.showErrorMessage(new ToastMessage(err.name, err.message, 10000)); } // Capture query time From 94d3a12fe052a17cb02b78ffd86d954b51a32045 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:07:27 +0200 Subject: [PATCH 006/122] test(core): adjust tests for ToastService --- .../toast-service/toast.service.spec.ts | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/app/core/services/toast-service/toast.service.spec.ts b/src/app/core/services/toast-service/toast.service.spec.ts index b632ff62a6..a7bf1a9dad 100644 --- a/src/app/core/services/toast-service/toast.service.spec.ts +++ b/src/app/core/services/toast-service/toast.service.spec.ts @@ -46,7 +46,7 @@ describe('ToastService (DONE)', () => { it('should have empty toast array', () => { expect(toastService.toasts).toBeTruthy(); - expect(toastService.toasts).toEqual([], 'should equal empty array []'); + expect(toastService.toasts).withContext('should equal empty array []').toEqual([]); }); describe('#add', () => { @@ -55,8 +55,8 @@ describe('ToastService (DONE)', () => { toastService.add(expectedToast1); expect(toastService.toasts).toBeTruthy(); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedToast1, `should equal ${expectedToast1}`); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast1}`).toEqual(expectedToast1); }); it('should add given template to toast array', () => { @@ -70,8 +70,8 @@ describe('ToastService (DONE)', () => { toastService.add(expectedTplToast); expect(toastService.toasts).toBeTruthy(); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedTplToast, `should equal ${expectedTplToast}`); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedTplToast}`).toEqual(expectedTplToast); }); it('should only add textOrTpl if options not given', () => { @@ -79,16 +79,17 @@ describe('ToastService (DONE)', () => { toastService.add(expectedToast1); expect(toastService.toasts).toBeTruthy(); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedToast1, `should equal ${expectedToast1}`); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast1}`).toEqual(expectedToast1); }); it('should add options if given with text message', () => { // Call service method toastService.add(expectedToast2); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedToast2, `should equal ${expectedToast2}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast2}`).toEqual(expectedToast2); }); it('should add options if given with template message', () => { @@ -101,8 +102,9 @@ describe('ToastService (DONE)', () => { // Call service method toastService.add(expectedTplToast); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedTplToast, `should equal ${expectedTplToast}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedTplToast}`).toEqual(expectedTplToast); }); }); @@ -118,9 +120,10 @@ describe('ToastService (DONE)', () => { // Call service method toastService.remove(expectedOtherToast); - expect(toastService.toasts.length).toBe(2, 'should have two entries'); - expect(toastService.toasts[0]).toEqual(expectedToast1, `should equal ${expectedToast1}`); - expect(toastService.toasts[1]).toEqual(expectedToast2, `should equal ${expectedToast2}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have two entries').toBe(2); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast1}`).toEqual(expectedToast1); + expect(toastService.toasts[1]).withContext(`should equal ${expectedToast2}`).toEqual(expectedToast2); }); it('should do nothing if options do not match', () => { @@ -128,27 +131,34 @@ describe('ToastService (DONE)', () => { // Call service method toastService.remove(otherOptionsToast); - expect(toastService.toasts.length).toBe(2, 'should have two entries'); - expect(toastService.toasts[0]).toEqual(expectedToast1, `should equal ${expectedToast1}`); - expect(toastService.toasts[1]).toEqual(expectedToast2, `should equal ${expectedToast2}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have two entries').toBe(2); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast1}`).toEqual(expectedToast1); + expect(toastService.toasts[1]).withContext(`should equal ${expectedToast2}`).toEqual(expectedToast2); }); it('should remove existing toast from toast array (without options)', () => { // Call service method toastService.remove(expectedToast1); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).not.toEqual(expectedToast1, `should not equal ${expectedToast1}`); - expect(toastService.toasts[0]).toEqual(expectedToast2, `should equal ${expectedToast2}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]) + .withContext(`should not equal ${expectedToast1}`) + .not.toEqual(expectedToast1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast2}`).toEqual(expectedToast2); }); it('should remove existing toast from toast array (with options)', () => { // Call service method toastService.remove(expectedToast2); - expect(toastService.toasts.length).toBe(1, 'should have one entry'); - expect(toastService.toasts[0]).toEqual(expectedToast1, `should equal ${expectedToast1}`); - expect(toastService.toasts[1]).not.toEqual(expectedToast2, `should not equal ${expectedToast2}`); + expect(toastService.toasts).toBeTruthy(); + expect(toastService.toasts.length).withContext('should have one entry').toBe(1); + expect(toastService.toasts[0]).withContext(`should equal ${expectedToast1}`).toEqual(expectedToast1); + expect(toastService.toasts[1]) + .withContext(`should not equal ${expectedToast2}`) + .not.toEqual(expectedToast2); }); }); }); From bd13db8e6a0059378cb97b6ffb22fa9324d88b88 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:12:15 +0200 Subject: [PATCH 007/122] fix(edition): add errorMessageRequest to SparqlEditor --- .../graph-visualizer.component.html | 11 ++++++----- .../sparql-editor/sparql-editor.component.ts | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html index a30320f9bd..4c42e221bc 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html @@ -7,20 +7,21 @@ + (updateTriplesRequest)="triples = $event">
-
+ + (updateQueryStringRequest)="query.queryString = $event">
diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts index e6cf2faaf9..2d0c628905 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts @@ -5,6 +5,7 @@ import { CmConfig } from '../models'; import { GraphSparqlQuery } from '@awg-views/edition-view/models'; import 'codemirror/mode/sparql/sparql'; +import { ToastMessage } from '@awg-core/services/toast-service'; /** * The SparqlEditor component. @@ -50,6 +51,14 @@ export class SparqlEditorComponent { @Input() isFullscreen: boolean; + /** + * Output variable: errorMessageRequest. + * + * It keeps an event emitter to update the query string after editor changes. + */ + @Output() + errorMessageRequest: EventEmitter = new EventEmitter(); + /** * Output variable: performQueryRequest. * @@ -151,7 +160,11 @@ export class SparqlEditorComponent { * @returns {void} Triggers the request. */ performQuery(): void { - this.performQueryRequest.emit(); + if (this.query.queryString) { + this.performQueryRequest.emit(); + } else { + this.errorMessageRequest.emit(new ToastMessage('Empty query', 'Please enter a SPARQL query.')); + } } /** From 1b90f03f91df1e6ee357dda5f31090190cc9e428 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:14:14 +0200 Subject: [PATCH 008/122] feat(edition): add Clear button to SparqlEditor --- .../sparql-editor/sparql-editor.component.html | 5 +++-- .../sparql-editor/sparql-editor.component.ts | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html index 77afa5d250..a5881b8e1b 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html @@ -53,8 +53,9 @@
- - + + +
diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts index 2d0c628905..94a720922e 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts @@ -117,16 +117,13 @@ export class SparqlEditorComponent { * Public method: onEditorInputChange. * * It emits the given query string - * to the {@link updateQueryRequest}. + * to the {@link updateQueryStringRequest}. * * @param {string} queryString The given query string. * * @returns {void} Emits the query. */ onEditorInputChange(queryString: string): void { - if (!queryString) { - return; - } this.updateQueryStringRequest.emit(queryString); } From e5c4880e97e971bfcd07e5a4fdb9efba7cc68332 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:16:31 +0200 Subject: [PATCH 009/122] test(edition): adjust tests for SparqlEditorComponent after changes --- .../sparql-editor.component.html | 5 +- .../sparql-editor.component.spec.ts | 273 +++++++++++++----- 2 files changed, 209 insertions(+), 69 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html index a5881b8e1b..490214e02c 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html @@ -8,7 +8,10 @@ -
+
diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts index 83c3ae7613..755a77e0df 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts @@ -23,6 +23,7 @@ import { GraphSparqlQuery } from '@awg-views/edition-view/models'; import { CmConfig } from '../models'; import { SparqlEditorComponent } from './sparql-editor.component'; +import { ToastMessage } from '@awg-core/services/toast-service'; // eslint-disable-next-line @angular-eslint/component-selector @Component({ selector: 'ngx-codemirror', template: '' }) @@ -51,6 +52,7 @@ describe('SparqlEditorComponent (DONE)', () => { let performQuerySpy: Spy; let preventPanelCollapseOnFullscreenSpy: Spy; let resetQuerySpy: Spy; + let emitErrorMessageRequestSpy: Spy; let emitPerformQueryRequestSpy: Spy; let emitResestQueryRequestSpy: Spy; let emitUpdateQueryStringRequestSpy: Spy; @@ -108,6 +110,7 @@ describe('SparqlEditorComponent (DONE)', () => { performQuerySpy = spyOn(component, 'performQuery').and.callThrough(); preventPanelCollapseOnFullscreenSpy = spyOn(component, 'preventPanelCollapseOnFullscreen').and.callThrough(); resetQuerySpy = spyOn(component, 'resetQuery').and.callThrough(); + emitErrorMessageRequestSpy = spyOn(component.errorMessageRequest, 'emit').and.callThrough(); emitPerformQueryRequestSpy = spyOn(component.performQueryRequest, 'emit').and.callThrough(); emitResestQueryRequestSpy = spyOn(component.resetQueryRequest, 'emit').and.callThrough(); emitUpdateQueryStringRequestSpy = spyOn(component.updateQueryStringRequest, 'emit').and.callThrough(); @@ -285,8 +288,13 @@ describe('SparqlEditorComponent (DONE)', () => { 1 ); - // Panel header div.btn-group - getAndExpectDebugElementByCss(panelHeaderDes[0], 'div.accordion-button > div.btn-group', 1, 1); + // Panel header div.awg-example-query-btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); }); it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { @@ -302,8 +310,13 @@ describe('SparqlEditorComponent (DONE)', () => { 1 ); - // Panel header div.btn-group - getAndExpectDebugElementByCss(panelHeaderDes[0], 'div.accordion-button > div.btn-group', 0, 0); + // Panel header div.awg-example-query-btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 0, + 0 + ); }); it('... should display a disabled button label in example query btn-group', () => { @@ -317,7 +330,7 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > button.btn', + 'div.accordion-button > div.awg-example-query-btn-group > button.btn', 1, 1 ); @@ -341,7 +354,7 @@ describe('SparqlEditorComponent (DONE)', () => { const outerButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group', + 'div.accordion-button > div.awg-example-query-btn-group', 1, 1 ); @@ -349,7 +362,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); }); - it('... should contain toggle button in btn-group dropdown', () => { + it('... should contain toggle button in example query btn-group dropdown', () => { // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( compDe, @@ -360,7 +373,7 @@ describe('SparqlEditorComponent (DONE)', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -368,7 +381,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'button.btn.dropdown-toggle', 1, 1); }); - it('... should contain dropdown menu div with dropdown item links in btn-group dropdown', fakeAsync(() => { + it('... should contain dropdown menu div with dropdown item links in example query btn-group dropdown', fakeAsync(() => { tick(); // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( @@ -380,7 +393,7 @@ describe('SparqlEditorComponent (DONE)', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -400,10 +413,10 @@ describe('SparqlEditorComponent (DONE)', () => { ); })); - it('... should display label on dropdown items', () => { + it('... should display label on dropdown items in example query btn-group', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( compDe, - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -527,7 +540,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with two buttons (Query, Reset) in panel body', () => { + it('... should contain div with three buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons', @@ -535,23 +548,27 @@ describe('SparqlEditorComponent (DONE)', () => { 1 ); - const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; const btnEl1 = btnDes[1].nativeElement; + const btnEl2 = btnDes[2].nativeElement; expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); }); it('... should trigger `performQuery()` by click on Query button', async () => { const btnDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl0 = btnDes[0].nativeElement; @@ -569,8 +586,8 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; @@ -583,6 +600,24 @@ describe('SparqlEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 0); expectSpyCall(resetQuerySpy, 1); }); + + it('... should trigger `onEditorInputChange()` with empty string by click on Clear button', async () => { + const btnDes = getAndExpectDebugElementByCss( + bodyDes[0], + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; + + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + await detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + }); }); }); @@ -718,7 +753,12 @@ describe('SparqlEditorComponent (DONE)', () => { ); // Panel header div.btn-group - getAndExpectDebugElementByCss(panelHeaderDes[0], 'div.accordion-button > div.btn-group', 1, 1); + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); }); it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { @@ -735,7 +775,12 @@ describe('SparqlEditorComponent (DONE)', () => { ); // Panel header div.btn-group - getAndExpectDebugElementByCss(panelHeaderDes[0], 'div.accordion-button > div.btn-group', 0, 0); + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 0, + 0 + ); }); it('... should display a disabled button label in example query btn-group', () => { @@ -749,7 +794,7 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > button.btn', + 'div.accordion-button > div.awg-example-query-btn-group > button.btn', 1, 1 ); @@ -773,7 +818,7 @@ describe('SparqlEditorComponent (DONE)', () => { const outerButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group', + 'div.accordion-button > div.awg-example-query-btn-group', 1, 1 ); @@ -781,7 +826,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); }); - it('... should contain toggle button in btn-group dropdown', () => { + it('... should contain toggle button in example query btn-group dropdown', () => { // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( compDe, @@ -792,7 +837,7 @@ describe('SparqlEditorComponent (DONE)', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -800,7 +845,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'button.btn.dropdown-toggle', 1, 1); }); - it('... should contain dropdown menu div with dropdown items', () => { + it('... should contain exmapleydropdown menu div with dropdown items in example query btn-group', () => { // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( compDe, @@ -811,7 +856,7 @@ describe('SparqlEditorComponent (DONE)', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( panelHeaderDes[0], - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -826,10 +871,10 @@ describe('SparqlEditorComponent (DONE)', () => { ); }); - it('... should display label on dropdown items', () => { + it('... should display label on dropdown items in example query btn-group', () => { const dropdownButtonGroupDes = getAndExpectDebugElementByCss( compDe, - 'div.accordion-button > div.btn-group > div.btn-group.dropdown', + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', 1, 1 ); @@ -859,7 +904,7 @@ describe('SparqlEditorComponent (DONE)', () => { .toContain(expectedQueryList[1].queryLabel); }); - it('... should disable current query in dropdown items', async () => { + it('... should disable current query in dropdown items in example query btn-group', async () => { const itemDes = getAndExpectDebugElementByCss( compDe, 'div.dropdown-menu > a.dropdown-item', @@ -926,7 +971,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with two buttons (Query, Reset) in panel body', () => { + it('... should contain div with three buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons', @@ -934,23 +979,27 @@ describe('SparqlEditorComponent (DONE)', () => { 1 ); - const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; const btnEl1 = btnDes[1].nativeElement; + const btnEl2 = btnDes[2].nativeElement; expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); }); it('... should trigger `performQuery()` by click on Query button', async () => { const btnDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl0 = btnDes[0].nativeElement; @@ -968,8 +1017,8 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; @@ -982,6 +1031,24 @@ describe('SparqlEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 0); expectSpyCall(resetQuerySpy, 1); }); + + it('... should trigger `onEditorInputChange()` with empty string by click on Clear button', async () => { + const btnDes = getAndExpectDebugElementByCss( + bodyDes[0], + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; + + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + await detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + }); }); }); @@ -1072,26 +1139,68 @@ describe('SparqlEditorComponent (DONE)', () => { expectSpyCall(onEditorInputChangeSpy, 1, changedQueryString); }); - it('... should not emit anything if no query string is provided', () => { - const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); - const codeMirrorCmp = codeMirrorDes[0].injector.get(CodeMirrorStubComponent) as CodeMirrorStubComponent; + it('... should trigger with empty string from click on Clear button', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; - // Query is undefined - codeMirrorCmp.ngModelChange.emit(''); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + await detectChangesOnPush(fixture); expectSpyCall(onEditorInputChangeSpy, 1, ''); - expectSpyCall(emitUpdateQueryStringRequestSpy, 0); }); - it('... should emit provided query string on editor change', () => { - const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); - const codeMirrorCmp = codeMirrorDes[0].injector.get(CodeMirrorStubComponent) as CodeMirrorStubComponent; + it('... should emit request on click', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; - const changedQueryString = expectedQuery2.queryString; - codeMirrorCmp.ngModelChange.emit(changedQueryString); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); - expectSpyCall(onEditorInputChangeSpy, 1, changedQueryString); - expectSpyCall(emitUpdateQueryStringRequestSpy, 1, changedQueryString); + // Click clear button + click(btnEl2 as HTMLElement); + await detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + }); + + describe('... should emit provided query string on editor change', () => { + it('... if string is thruthy', () => { + const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); + const codeMirrorCmp = codeMirrorDes[0].injector.get( + CodeMirrorStubComponent + ) as CodeMirrorStubComponent; + + const changedQueryString = expectedQuery2.queryString; + codeMirrorCmp.ngModelChange.emit(changedQueryString); + + expectSpyCall(onEditorInputChangeSpy, 1, changedQueryString); + expectSpyCall(emitUpdateQueryStringRequestSpy, 1, changedQueryString); + }); + + it('... if string is empty', () => { + const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); + const codeMirrorCmp = codeMirrorDes[0].injector.get( + CodeMirrorStubComponent + ) as CodeMirrorStubComponent; + + // Query is undefined + codeMirrorCmp.ngModelChange.emit(''); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + expectSpyCall(emitUpdateQueryStringRequestSpy, 1, ''); + }); }); }); @@ -1209,8 +1318,8 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( compDe, 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl0 = btnDes[0].nativeElement; @@ -1223,23 +1332,51 @@ describe('SparqlEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 1); }); - it('... should emit request on click', async () => { - const btnDes = getAndExpectDebugElementByCss( - compDe, - 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 - ); - const btnEl0 = btnDes[0].nativeElement; + describe('... should emit on click:', async () => { + it('`performQueryRequest` if querystring is given', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl0 = btnDes[0].nativeElement; - expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); + expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); - // Click query button - click(btnEl0 as HTMLElement); - await detectChangesOnPush(fixture); + // Click query button + click(btnEl0 as HTMLElement); + await detectChangesOnPush(fixture); - expectSpyCall(performQuerySpy, 1); - expectSpyCall(emitPerformQueryRequestSpy, 1); + expectSpyCall(performQuerySpy, 1); + expectSpyCall(emitPerformQueryRequestSpy, 1); + expectSpyCall(emitErrorMessageRequestSpy, 0); + }); + + it('`errorMessageRequest` with errorMessage if querystring is not given', async () => { + const expectedErrorMessage = new ToastMessage('Empty query', 'Please enter a SPARQL query.'); + + component.query.queryString = ''; + await detectChangesOnPush(fixture); + + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl0 = btnDes[0].nativeElement; + + expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); + + // Click query button + click(btnEl0 as HTMLElement); + await detectChangesOnPush(fixture); + + expectSpyCall(performQuerySpy, 1); + expectSpyCall(emitPerformQueryRequestSpy, 0); + expectSpyCall(emitErrorMessageRequestSpy, 1, expectedErrorMessage); + }); }); }); @@ -1263,8 +1400,8 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( compDe, 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; @@ -1290,8 +1427,8 @@ describe('SparqlEditorComponent (DONE)', () => { const btnDes = getAndExpectDebugElementByCss( compDe, 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', - 2, - 2 + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; From 1625ae34dd94a909ab67a5a691beba4c7f179c83 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:53:41 +0200 Subject: [PATCH 010/122] fix(edition): add errorMessageRequest to TriplesEditor --- .../graph-visualizer.component.html | 1 + .../triples-editor/triples-editor.component.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html index 4c42e221bc..5e7f9037c6 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.html @@ -7,6 +7,7 @@ diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts index daf7544c11..8171005132 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; +import { ToastMessage } from '@awg-core/services/toast-service'; import { CmConfig } from '../models'; import 'codemirror/mode/turtle/turtle'; @@ -41,6 +42,14 @@ export class TriplesEditorComponent { @Input() isFullscreen: boolean; + /** + * Output variable: errorMessageRequest. + * + * It keeps an event emitter to update the query string after editor changes. + */ + @Output() + errorMessageRequest: EventEmitter = new EventEmitter(); + /** * Output variable: performQueryRequest. * @@ -104,7 +113,11 @@ export class TriplesEditorComponent { * @returns {void} Triggers the request. */ performQuery(): void { - this.performQueryRequest.emit(); + if (this.triples) { + this.performQueryRequest.emit(); + } else { + this.errorMessageRequest.emit(new ToastMessage('Empty triples', 'Please enter triple content.')); + } } /** From 75a8dd9f0b0d9f33dba76d619264a09031a65103 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:55:14 +0200 Subject: [PATCH 011/122] feat(edition): add Clear button to TriplesEditor --- .../triples-editor/triples-editor.component.html | 7 ++++--- .../triples-editor/triples-editor.component.ts | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.html index 1fb3e54a51..e909c58a0b 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.html @@ -12,9 +12,10 @@ -
- - +
+ + +
diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts index 8171005132..d8c4873490 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts @@ -98,9 +98,6 @@ export class TriplesEditorComponent { * @returns {void} Emits the triples. */ onEditorInputChange(triples: string): void { - if (!triples) { - return; - } this.updateTriplesRequest.emit(triples); } From 456ddc523739adaae88e6b12cb04bce0edd6b0bc Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 14:58:40 +0200 Subject: [PATCH 012/122] test(edition): adjust tests for TriplesEditorComponent after changes --- .../triples-editor.component.spec.ts | 205 ++++++++++++++---- 1 file changed, 160 insertions(+), 45 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts index c36969d8d0..a6970c4d14 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts @@ -12,7 +12,9 @@ import { getAndExpectDebugElementByDirective, } from '@testing/expect-helper'; +import { ToastMessage } from '@awg-core/services/toast-service'; import { CmConfig } from '../models'; + import { TriplesEditorComponent } from './triples-editor.component'; // eslint-disable-next-line @angular-eslint/component-selector @@ -38,6 +40,7 @@ describe('TriplesEditorComponent (DONE)', () => { let performQuerySpy: Spy; let preventPanelCollapseOnFullscreenSpy: Spy; let resetTriplesSpy: Spy; + let emitErrorMessageSpy: Spy; let emitPerformQueryRequestSpy: Spy; let emitResetTriplesRequestSpy: Spy; let emitUpdateTriplesRequestSpy: Spy; @@ -82,6 +85,7 @@ describe('TriplesEditorComponent (DONE)', () => { performQuerySpy = spyOn(component, 'performQuery').and.callThrough(); preventPanelCollapseOnFullscreenSpy = spyOn(component, 'preventPanelCollapseOnFullscreen').and.callThrough(); resetTriplesSpy = spyOn(component, 'resetTriples').and.callThrough(); + emitErrorMessageSpy = spyOn(component.errorMessageRequest, 'emit').and.callThrough(); emitPerformQueryRequestSpy = spyOn(component.performQueryRequest, 'emit').and.callThrough(); emitResetTriplesRequestSpy = spyOn(component.resetTriplesRequest, 'emit').and.callThrough(); emitUpdateTriplesRequestSpy = spyOn(component.updateTriplesRequest, 'emit').and.callThrough(); @@ -333,22 +337,26 @@ describe('TriplesEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with two buttons (Query, Reset) in panel body', () => { + it('... should contain div with 3 buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss(bodyDes[0], 'div', 1, 1); - const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; const btnEl1 = btnDes[1].nativeElement; + const btnEl2 = btnDes[2].nativeElement; expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); }); it('... should trigger `performQuery()` by click on Query button', () => { - const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; expect(btnEl0.textContent).toBeTruthy(); @@ -363,7 +371,7 @@ describe('TriplesEditorComponent (DONE)', () => { }); it('... should trigger `resetTriples()` by click on Reset button', () => { - const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); const btnEl1 = btnDes[1].nativeElement; expect(btnEl1.textContent).toBeTruthy(); @@ -376,6 +384,21 @@ describe('TriplesEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 0); expectSpyCall(resetTriplesSpy, 1); }); + + it('... should trigger `onEditorInputChange()` with empty string by click on Clear button', () => { + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); + const btnEl2 = btnDes[2].nativeElement; + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1); + expectSpyCall(onEditorInputChangeSpy, 1, ''); + }); }); }); @@ -489,22 +512,26 @@ describe('TriplesEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with two buttons (Query, Reset) in panel body', () => { + it('... should contain div with 3 buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss(bodyDes[0], 'div', 1, 1); - const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(divDes[0], 'button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; const btnEl1 = btnDes[1].nativeElement; + const btnEl2 = btnDes[2].nativeElement; expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); }); it('... should trigger `performQuery()` by click on Query button', () => { - const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); const btnEl0 = btnDes[0].nativeElement; expect(btnEl0.textContent).toBeTruthy(); @@ -519,7 +546,7 @@ describe('TriplesEditorComponent (DONE)', () => { }); it('... should trigger `resetTriples()` by click on Reset button', () => { - const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 2, 2); + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); const btnEl1 = btnDes[1].nativeElement; expect(btnEl1.textContent).toBeTruthy(); @@ -532,6 +559,21 @@ describe('TriplesEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 0); expectSpyCall(resetTriplesSpy, 1); }); + + it('... should trigger `onEditorInputChange()` with empty string by click on Clear button', () => { + const btnDes = getAndExpectDebugElementByCss(bodyDes[0], 'div > button.btn', 3, 3); + const btnEl2 = btnDes[2].nativeElement; + + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1); + expectSpyCall(onEditorInputChangeSpy, 1, ''); + }); }); }); @@ -564,26 +606,70 @@ describe('TriplesEditorComponent (DONE)', () => { expectSpyCall(onEditorInputChangeSpy, 1, changedTriples); }); - it('... should not emit anything if no triples are provided', () => { - const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); - const codeMirrorCmp = codeMirrorDes[0].injector.get(CodeMirrorStubComponent) as CodeMirrorStubComponent; + it('... should trigger with empty string from click on Clear button', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; - // Triples are undefined - codeMirrorCmp.ngModelChange.emit(''); + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); + + // Click clear button + click(btnEl2 as HTMLElement); + detectChangesOnPush(fixture); expectSpyCall(onEditorInputChangeSpy, 1, ''); - expectSpyCall(emitUpdateTriplesRequestSpy, 0); }); - it('... should emit provided triples on editor change', () => { - const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); - const codeMirrorCmp = codeMirrorDes[0].injector.get(CodeMirrorStubComponent) as CodeMirrorStubComponent; + it('... should emit updateTriplesRequest on click', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl2 = btnDes[2].nativeElement; - const changedTriples = 'example:Success example:is example:Testing'; - codeMirrorCmp.ngModelChange.emit(changedTriples); + expect(btnEl2.textContent).toBeTruthy(); + expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); - expectSpyCall(onEditorInputChangeSpy, 1, changedTriples); - expectSpyCall(emitUpdateTriplesRequestSpy, 1, changedTriples); + // Click clear button + click(btnEl2 as HTMLElement); + detectChangesOnPush(fixture); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + expectSpyCall(emitUpdateTriplesRequestSpy, 1); + }); + + describe('... should emit provided triples on editor change', () => { + it('... if string is truthy', () => { + const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); + const codeMirrorCmp = codeMirrorDes[0].injector.get( + CodeMirrorStubComponent + ) as CodeMirrorStubComponent; + + const changedTriples = 'example:Success example:is example:Testing'; + codeMirrorCmp.ngModelChange.emit(changedTriples); + + expectSpyCall(onEditorInputChangeSpy, 1, changedTriples); + expectSpyCall(emitUpdateTriplesRequestSpy, 1, changedTriples); + }); + + it('... if string is empty', () => { + const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); + const codeMirrorCmp = codeMirrorDes[0].injector.get( + CodeMirrorStubComponent + ) as CodeMirrorStubComponent; + + codeMirrorCmp.ngModelChange.emit(''); + + expectSpyCall(onEditorInputChangeSpy, 1, ''); + expectSpyCall(emitUpdateTriplesRequestSpy, 1, ''); + }); }); }); @@ -609,9 +695,9 @@ describe('TriplesEditorComponent (DONE)', () => { it('... should trigger on click on Query button', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, - 'div#awg-graph-visualizer-triples > div.accordion-body > div > button.btn', - 2, - 2 + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 ); const btnEl0 = btnDes[0].nativeElement; @@ -625,24 +711,53 @@ describe('TriplesEditorComponent (DONE)', () => { expectSpyCall(performQuerySpy, 1); }); - it('... should emit request on click', async () => { - const btnDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-triples > div.accordion-body > div > button.btn', - 2, - 2 - ); - const btnEl0 = btnDes[0].nativeElement; + describe('... should emit on click', () => { + it('`performQueryRequest` if querystring is given', async () => { + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl0 = btnDes[0].nativeElement; - expect(btnEl0.textContent).toBeTruthy(); - expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); + expect(btnEl0.textContent).toBeTruthy(); + expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); - // Click query button - click(btnEl0 as HTMLElement); - await detectChangesOnPush(fixture); + // Click query button + click(btnEl0 as HTMLElement); + await detectChangesOnPush(fixture); - expectSpyCall(performQuerySpy, 1); - expectSpyCall(emitPerformQueryRequestSpy, 1); + expectSpyCall(performQuerySpy, 1); + expectSpyCall(emitPerformQueryRequestSpy, 1); + expectSpyCall(emitErrorMessageSpy, 0); + }); + + it('`errorMessageRequest` with errorMessage if querystring is not given', async () => { + const expectedErrorMessage = new ToastMessage('Empty triples', 'Please enter triple content.'); + + component.triples = ''; + await detectChangesOnPush(fixture); + + const btnDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 + ); + const btnEl0 = btnDes[0].nativeElement; + + expect(btnEl0.textContent).toBeTruthy(); + expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); + + // Click query button + click(btnEl0 as HTMLElement); + await detectChangesOnPush(fixture); + + expectSpyCall(performQuerySpy, 1); + expectSpyCall(emitPerformQueryRequestSpy, 0); + expectSpyCall(emitErrorMessageSpy, 1, expectedErrorMessage); + }); }); }); @@ -668,9 +783,9 @@ describe('TriplesEditorComponent (DONE)', () => { it('... should trigger on click on Reset button', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, - 'div#awg-graph-visualizer-triples > div.accordion-body > div > button.btn', - 2, - 2 + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; @@ -687,9 +802,9 @@ describe('TriplesEditorComponent (DONE)', () => { it('... should emit request on click', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, - 'div#awg-graph-visualizer-triples > div.accordion-body > div > button.btn', - 2, - 2 + 'div.awg-graph-visualizer-triples-handle-buttons > button.btn', + 3, + 3 ); const btnEl1 = btnDes[1].nativeElement; From fdbe9a0d554c28cc09be715e84c63b31f4c47c0b Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 15:03:44 +0200 Subject: [PATCH 013/122] test(edition): adjust tests for SparqlEditorComponent after changes --- .../sparql-editor.component.spec.ts | 24 +++++++++++++++---- .../sparql-editor/sparql-editor.component.ts | 6 +++-- .../triples-editor.component.spec.ts | 4 ++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts index 755a77e0df..f45d960b8d 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts @@ -216,7 +216,7 @@ describe('SparqlEditorComponent (DONE)', () => { const btnEl = btnDes[0].nativeElement; // Check button content - expect(btnEl.textContent).toBeDefined(); + expect(btnEl.textContent).toBeTruthy(); expect(btnEl.textContent).withContext('should be SPARQL').toContain('SPARQL'); }); @@ -540,7 +540,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with three buttons (Query, Reset, Clear) in panel body', () => { + it('... should contain div with 3 buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons', @@ -572,6 +572,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl0 = btnDes[0].nativeElement; + expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); // Click query button @@ -591,6 +592,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl1 = btnDes[1].nativeElement; + expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); // Click reset button @@ -610,6 +612,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl2 = btnDes[2].nativeElement; + expect(btnEl2.textContent).toBeTruthy(); expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); // Click clear button @@ -693,7 +696,7 @@ describe('SparqlEditorComponent (DONE)', () => { const btnEl = btnDes[0].nativeElement; // Check button content - expect(btnEl.textContent).toBeDefined(); + expect(btnEl.textContent).toBeTruthy(); expect(btnEl.textContent).withContext(`should be 'SPARQL'`).toContain('SPARQL'); }); @@ -971,7 +974,7 @@ describe('SparqlEditorComponent (DONE)', () => { getAndExpectDebugElementByDirective(bodyDes[0], CodeMirrorStubComponent, 1, 1); }); - it('... should contain div with three buttons (Query, Reset, Clear) in panel body', () => { + it('... should contain div with 3 buttons (Query, Reset, Clear) in panel body', () => { const divDes = getAndExpectDebugElementByCss( bodyDes[0], 'div.awg-graph-visualizer-sparql-query-handle-buttons', @@ -1003,6 +1006,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl0 = btnDes[0].nativeElement; + expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); // Click query button @@ -1022,6 +1026,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl1 = btnDes[1].nativeElement; + expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); // Click reset button @@ -1041,6 +1046,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl2 = btnDes[2].nativeElement; + expect(btnEl2.textContent).toBeTruthy(); expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); // Click clear button @@ -1148,6 +1154,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl2 = btnDes[2].nativeElement; + expect(btnEl2.textContent).toBeTruthy(); expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); // Click clear button @@ -1157,7 +1164,7 @@ describe('SparqlEditorComponent (DONE)', () => { expectSpyCall(onEditorInputChangeSpy, 1, ''); }); - it('... should emit request on click', async () => { + it('... should emit updateQueryStringRequest on click', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, 'div.awg-graph-visualizer-sparql-query-handle-buttons > button.btn', @@ -1166,6 +1173,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl2 = btnDes[2].nativeElement; + expect(btnEl2.textContent).toBeTruthy(); expect(btnEl2.textContent).withContext(`should contain 'Clear'`).toContain('Clear'); // Click clear button @@ -1173,6 +1181,7 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); expectSpyCall(onEditorInputChangeSpy, 1, ''); + expectSpyCall(emitUpdateQueryStringRequestSpy, 1); }); describe('... should emit provided query string on editor change', () => { @@ -1323,6 +1332,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl0 = btnDes[0].nativeElement; + expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); // Click query button @@ -1342,6 +1352,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl0 = btnDes[0].nativeElement; + expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); // Click query button @@ -1367,6 +1378,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl0 = btnDes[0].nativeElement; + expect(btnEl0.textContent).toBeTruthy(); expect(btnEl0.textContent).withContext(`should contain 'Query'`).toContain('Query'); // Click query button @@ -1405,6 +1417,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl1 = btnDes[1].nativeElement; + expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); // Click query button @@ -1432,6 +1445,7 @@ describe('SparqlEditorComponent (DONE)', () => { ); const btnEl1 = btnDes[1].nativeElement; + expect(btnEl1.textContent).toBeTruthy(); expect(btnEl1.textContent).withContext(`should contain 'Reset'`).toContain('Reset'); // Click reset button diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts index 94a720922e..70883e3455 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts @@ -1,11 +1,13 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; + import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; +import { faDiagramProject, faTable } from '@fortawesome/free-solid-svg-icons'; -import { CmConfig } from '../models'; +import { ToastMessage } from '@awg-core/services/toast-service'; import { GraphSparqlQuery } from '@awg-views/edition-view/models'; +import { CmConfig } from '../models'; import 'codemirror/mode/sparql/sparql'; -import { ToastMessage } from '@awg-core/services/toast-service'; /** * The SparqlEditor component. diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts index a6970c4d14..62fdd087cf 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts @@ -191,7 +191,7 @@ describe('TriplesEditorComponent (DONE)', () => { const btnEl = btnDes[0].nativeElement; // Check button content - expect(btnEl.textContent).toBeDefined(); + expect(btnEl.textContent).toBeTruthy(); expect(btnEl.textContent).withContext('should be RDF Triples').toContain('RDF Triples'); }); @@ -466,7 +466,7 @@ describe('TriplesEditorComponent (DONE)', () => { const btnEl = btnDes[0].nativeElement; // Check button content - expect(btnEl.textContent).toBeDefined(); + expect(btnEl.textContent).toBeTruthy(); expect(btnEl.textContent).withContext('should be RDF Triples').toContain('RDF Triples'); }); From 1d701c091dbbe4cd4dd4c9df5cad1846e2ebe1b4 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 15:53:52 +0200 Subject: [PATCH 014/122] test(edition): adjust tests for GraphVisualizerComponent after changes --- .../graph-visualizer.component.spec.ts | 199 ++++++++++++------ .../graph-visualizer.component.ts | 6 +- 2 files changed, 132 insertions(+), 73 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts index 00a94c0866..2b7c62e5b9 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts @@ -13,7 +13,7 @@ import { } from '@testing/expect-helper'; import { mockConsole } from '@testing/mock-helper'; -import { Toast, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastMessage, ToastService } from '@awg-core/services/toast-service'; import { GraphRDFData, GraphSparqlQuery } from '@awg-views/edition-view/models'; import { D3SimulationNode, D3SimulationNodeType, Triple } from './models'; @@ -55,6 +55,8 @@ class SparqlEditorStubComponent { @Input() isFullscreen: boolean; @Output() + errorMessageRequest: EventEmitter = new EventEmitter(); + @Output() performQueryRequest: EventEmitter = new EventEmitter(); @Output() resetQueryRequest: EventEmitter = new EventEmitter(); @@ -71,6 +73,8 @@ class TriplesEditorStubComponent { @Input() isFullscreen: boolean; @Output() + errorMessageRequest: EventEmitter = new EventEmitter(); + @Output() performQueryRequest: EventEmitter = new EventEmitter(); @Output() resetTriplesRequest: EventEmitter = new EventEmitter(); @@ -102,6 +106,7 @@ describe('GraphVisualizerComponent (DONE)', () => { let performQuerySpy: Spy; let resetQuerySpy: Spy; let resetTriplesSpy: Spy; + let toastServiceAddSpy: Spy; beforeEach(waitForAsync(() => { // Mocked dataStreamerService @@ -171,6 +176,7 @@ describe('GraphVisualizerComponent (DONE)', () => { performQuerySpy = spyOn(component, 'performQuery').and.callThrough(); resetQuerySpy = spyOn(component, 'resetQuery').and.callThrough(); resetTriplesSpy = spyOn(component, 'resetTriples').and.callThrough(); + toastServiceAddSpy = spyOn(toastService, 'add').and.callThrough(); }); afterEach(() => { @@ -310,6 +316,17 @@ describe('GraphVisualizerComponent (DONE)', () => { }); describe('#resetTriples', () => { + it('... should trigger on resetTriplesRequest event from TriplesEditorComponent', () => { + expectSpyCall(resetTriplesSpy, 1, undefined); + + const editorDes = getAndExpectDebugElementByDirective(compDe, TriplesEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(TriplesEditorStubComponent) as TriplesEditorStubComponent; + + editorCmp.resetTriplesRequest.emit(); + + expectSpyCall(resetTriplesSpy, 2, undefined); + }); + it('... should set initial triples', () => { expectSpyCall(resetTriplesSpy, 1, undefined); @@ -358,20 +375,21 @@ describe('GraphVisualizerComponent (DONE)', () => { expectSpyCall(resetTriplesSpy, 2); expect(component.triples).toBeUndefined(); }); + }); - it('... should trigger on resetTriplesRequest event from TriplesEditorComponent', () => { - expectSpyCall(resetTriplesSpy, 1, undefined); + describe('#resetQuery', () => { + it('... should trigger on resetQueryRequest event from SparqlEditorComponent', () => { + expectSpyCall(resetQuerySpy, 1, undefined); - const editorDes = getAndExpectDebugElementByDirective(compDe, TriplesEditorStubComponent, 1, 1); - const editorCmp = editorDes[0].injector.get(TriplesEditorStubComponent) as TriplesEditorStubComponent; + const editorDes = getAndExpectDebugElementByDirective(compDe, SparqlEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(SparqlEditorStubComponent) as SparqlEditorStubComponent; - editorCmp.resetTriplesRequest.emit(); + // Set changed query + editorCmp.resetQueryRequest.emit(expectedGraphRDFData.queryList[1]); - expectSpyCall(resetTriplesSpy, 2, undefined); + expectSpyCall(resetQuerySpy, 2, expectedGraphRDFData.queryList[1]); }); - }); - describe('#resetQuery', () => { it('... should set initial queryList', () => { expectSpyCall(resetQuerySpy, 1, undefined); @@ -544,18 +562,6 @@ describe('GraphVisualizerComponent (DONE)', () => { expect(component.queryList).toBeUndefined(); }); - it('... should trigger on resetQueryRequest event from SparqlEditorComponent', () => { - expectSpyCall(resetQuerySpy, 1, undefined); - - const editorDes = getAndExpectDebugElementByDirective(compDe, SparqlEditorStubComponent, 1, 1); - const editorCmp = editorDes[0].injector.get(SparqlEditorStubComponent) as SparqlEditorStubComponent; - - // Set changed query - editorCmp.resetQueryRequest.emit(expectedGraphRDFData.queryList[1]); - - expectSpyCall(resetQuerySpy, 2, expectedGraphRDFData.queryList[1]); - }); - it('... should trigger `performQuery()`', () => { expectSpyCall(performQuerySpy, 1, undefined); @@ -569,6 +575,32 @@ describe('GraphVisualizerComponent (DONE)', () => { }); describe('#performQuery', () => { + it('... should trigger on event from TriplesEditorComponent', () => { + // First time called on ngOnInit + expectSpyCall(performQuerySpy, 1, undefined); + + const editorDes = getAndExpectDebugElementByDirective(compDe, TriplesEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(TriplesEditorStubComponent) as TriplesEditorStubComponent; + + // Set changed query + editorCmp.performQueryRequest.emit(); + + expectSpyCall(performQuerySpy, 2); + }); + + it('... should trigger on event from SparqlEditorComponent', () => { + // First time called on ngOnInit + expectSpyCall(performQuerySpy, 1, undefined); + + const editorDes = getAndExpectDebugElementByDirective(compDe, SparqlEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(SparqlEditorStubComponent) as SparqlEditorStubComponent; + + // Set changed query + editorCmp.performQueryRequest.emit(); + + expectSpyCall(performQuerySpy, 2); + }); + it('... should append namespaces to query if no prefixes given', () => { expectSpyCall(performQuerySpy, 1, undefined); @@ -748,7 +780,7 @@ describe('GraphVisualizerComponent (DONE)', () => { .toBeResolvedTo(expectedResult); })); - it('... should return Empty on error', waitForAsync(() => { + it('... should return empty array on error', waitForAsync(() => { const expectedCallback = [ 'construct', expectedGraphRDFData.queryList[0].queryString, @@ -802,6 +834,7 @@ describe('GraphVisualizerComponent (DONE)', () => { expectedGraphRDFData.triples, ]; const expectedError = { name: 'Error', message: 'error message' }; + const expectedToastMessage = new ToastMessage(expectedError.name, expectedError.message, 5000); spyOn(console, 'error').and.callFake(mockConsole.log); // Catch console output spyOn(graphVisualizerService, 'doQuery').and.callFake(() => Promise.reject(expectedError)); @@ -815,6 +848,12 @@ describe('GraphVisualizerComponent (DONE)', () => { ).toBeRejectedWith(expectedError); expectSpyCall(showErrorMessageSpy, 1); + expect(showErrorMessageSpy.calls.any()).toBeTruthy(); + expect(showErrorMessageSpy.calls.count()).withContext(`should be 1`).toBe(1); + expect(showErrorMessageSpy.calls.first().args) + .withContext(`should equal ${expectedToastMessage}`) + .toEqual([expectedToastMessage]); + expect(showErrorMessageSpy.calls.allArgs()[0]).withContext(`should equal ${expectedToastMessage}`); }); it('... should trigger `showErrorMessage` 2x if error message contains `undefined`', async () => { @@ -825,6 +864,9 @@ describe('GraphVisualizerComponent (DONE)', () => { ]; const expectedError = { name: 'Error', message: 'error message undefined' }; + const expectedToastMessage1 = new ToastMessage('Error', 'The query did not return any results.', 5000); + const expectedToastMessage2 = new ToastMessage(expectedError.name, expectedError.message, 5000); + spyOn(console, 'error').and.callFake(mockConsole.log); // Catch console output spyOn(graphVisualizerService, 'doQuery').and.callFake(() => Promise.reject(expectedError)); @@ -838,23 +880,19 @@ describe('GraphVisualizerComponent (DONE)', () => { expectSpyCall(showErrorMessageSpy, 2); expect(showErrorMessageSpy.calls.any()).toBeTruthy(); - expect(showErrorMessageSpy.calls.count()).toBe(2); - expect(showErrorMessageSpy.calls.first().args).toEqual([ - 'Error', - 'The query did not return any results', - 10000, - ]); - expect(showErrorMessageSpy.calls.allArgs()[0]).toEqual([ - 'Error', - 'The query did not return any results', - 10000, - ]); - expect(showErrorMessageSpy.calls.allArgs()[1]).toEqual(['Error', 'error message undefined', 10000]); - expect(showErrorMessageSpy.calls.mostRecent().args).toEqual([ - 'Error', - 'error message undefined', - 10000, - ]); + expect(showErrorMessageSpy.calls.count()).withContext(`should be 2`).toBe(2); + expect(showErrorMessageSpy.calls.first().args) + .withContext(`should equal ${expectedToastMessage1}`) + .toEqual([expectedToastMessage1]); + expect(showErrorMessageSpy.calls.allArgs()[0]) + .withContext(`should equal ${expectedToastMessage1}`) + .toEqual([expectedToastMessage1]); + expect(showErrorMessageSpy.calls.allArgs()[1]) + .withContext(`should equal ${expectedToastMessage2}`) + .toEqual([expectedToastMessage2]); + expect(showErrorMessageSpy.calls.mostRecent().args) + .withContext(`should equal ${expectedToastMessage2}`) + .toEqual([expectedToastMessage2]); }); }); @@ -870,67 +908,88 @@ describe('GraphVisualizerComponent (DONE)', () => { consoleSpy = spyOn(console, 'error').and.callFake(mockConsole.log); }); - it('... should not do anything if no message is provided', () => { - component.showErrorMessage('Error', '', 500); + it('... should trigger on event from TriplesEditorComponent', () => { + const editorDes = getAndExpectDebugElementByDirective(compDe, TriplesEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(TriplesEditorStubComponent) as TriplesEditorStubComponent; + + // Set changed query + editorCmp.errorMessageRequest.emit(new ToastMessage('Test', 'test message')); + + expectSpyCall(showErrorMessageSpy, 1); + }); + + it('... should trigger on event from SparqlEditorComponent', () => { + const editorDes = getAndExpectDebugElementByDirective(compDe, SparqlEditorStubComponent, 1, 1); + const editorCmp = editorDes[0].injector.get(SparqlEditorStubComponent) as SparqlEditorStubComponent; + + // Set changed query + editorCmp.errorMessageRequest.emit(new ToastMessage('Test', 'test message')); - expectSpyCall(showErrorMessageSpy, 1, ['Error', '', 500]); - expectSpyCall(consoleSpy, 0); + expectSpyCall(showErrorMessageSpy, 1); }); - it('... should set durationvValue = 7000 if not given', () => { - component.showErrorMessage('Error', 'error message'); + describe('... should not do anything', () => { + it('... if no toastMessage is provided', () => { + component.showErrorMessage(undefined); + + expectSpyCall(showErrorMessageSpy, 1, [undefined]); + expectSpyCall(toastServiceAddSpy, 0); + expectSpyCall(consoleSpy, 0); + }); - expectSpyCall(showErrorMessageSpy, 1, ['Error', 'error message']); - expectSpyCall(consoleSpy, 1, ['error message', 7000]); + it('... if no toastMessage.message is provided', () => { + const toastMessage = new ToastMessage('Error1', '', 500); + component.showErrorMessage(undefined); + + expectSpyCall(showErrorMessageSpy, 1, [undefined]); + expectSpyCall(toastServiceAddSpy, 0); + expectSpyCall(consoleSpy, 0); + }); }); - it('... should log the provided message and durationValue to console', () => { - component.showErrorMessage('Error1', 'error message', 500); + it('... should log the provided name and message to console', () => { + const toastMessage = new ToastMessage('Error1', 'error message', 500); + component.showErrorMessage(toastMessage); - expectSpyCall(showErrorMessageSpy, 1, ['Error1', 'error message', 500]); - expectSpyCall(consoleSpy, 1, ['error message', 500]); + expectSpyCall(showErrorMessageSpy, 1, toastMessage); + expectSpyCall(consoleSpy, 1, [toastMessage.name, ':', toastMessage.message]); }); it('... should trigger toast service and add a toast message', () => { - const name = 'Error1'; - const message = 'error message'; - const delay = 500; + const toastMessage = new ToastMessage('Error1', 'error message', 500); - const toastSpy = spyOn(toastService, 'add').and.callThrough(); - const expectedToast = new Toast(message, { - header: name, + const expectedToast = new Toast(toastMessage.message, { + header: toastMessage.name, classname: 'bg-danger text-light', - delay: delay, + delay: toastMessage.duration, }); // Trigger error message - component.showErrorMessage(name, message, delay); + component.showErrorMessage(toastMessage); fixture.detectChanges(); - expectSpyCall(toastSpy, 1, expectedToast); + expectSpyCall(toastServiceAddSpy, 1, expectedToast); expect(toastService.toasts).toBeDefined(); expect(toastService.toasts.length).withContext(`should be 1`).toBe(1); expect(toastService.toasts[0]).withContext(`should equal ${expectedToast}`).toEqual(expectedToast); }); - it('... should set durationvValue = 7000 for the toast message if delay not given ', () => { - const name = 'Error1'; - const message = 'error message'; - const delay = 7000; + it('... should set durationvValue = 3000 for the toast message if delay not given ', () => { + const toastMessage = new ToastMessage('Error1', 'error message'); + const expectedDuration = 3000; - const toastSpy = spyOn(toastService, 'add').and.callThrough(); - const expectedToast = new Toast(message, { - header: name, + const expectedToast = new Toast(toastMessage.message, { + header: toastMessage.name, classname: 'bg-danger text-light', - delay: delay, + delay: expectedDuration, }); // Trigger error message without delay value - component.showErrorMessage(name, message); + component.showErrorMessage(toastMessage); fixture.detectChanges(); - expectSpyCall(toastSpy, 1, expectedToast); + expectSpyCall(toastServiceAddSpy, 1, expectedToast); expect(toastService.toasts).toBeDefined(); expect(toastService.toasts.length).withContext(`should be 1`).toBe(1); diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts index 6e949e0380..549c89eb74 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts @@ -222,7 +222,7 @@ export class GraphVisualizerComponent implements OnInit { * @returns {void} Shows the error message. */ showErrorMessage(toastMessage: ToastMessage): void { - if (!toastMessage) { + if (!toastMessage || !toastMessage.message) { return; } @@ -264,9 +264,9 @@ export class GraphVisualizerComponent implements OnInit { if (err.message && err.name) { if (err.message.indexOf('undefined') !== -1) { - this.showErrorMessage(new ToastMessage(err.name, 'The query did not return any results', 10000)); + this.showErrorMessage(new ToastMessage(err.name, 'The query did not return any results.', 5000)); } - this.showErrorMessage(new ToastMessage(err.name, err.message, 10000)); + this.showErrorMessage(new ToastMessage(err.name, err.message, 5000)); } // Capture query time From 31795613fdc8cde4a145e515774d56544260f98a Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 23:40:20 +0200 Subject: [PATCH 015/122] refactor(shared): rename formatInput method in TablePagination --- .../table-pagination/table-pagination.component.html | 2 +- .../table/table-pagination/table-pagination.component.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/shared/table/table-pagination/table-pagination.component.html b/src/app/shared/table/table-pagination/table-pagination.component.html index 7ea345dda2..d4cad62168 100644 --- a/src/app/shared/table/table-pagination/table-pagination.component.html +++ b/src/app/shared/table/table-pagination/table-pagination.component.html @@ -18,7 +18,7 @@ [value]="page" (keyup.enter)="selectPage(i.value)" (blur)="selectPage(i.value)" - (input)="formatInput($any($event).target)" + (input)="replaceNonNumberInput($any($event).target)" aria-labelledby="paginationInputLabel paginationDescription" style="width: 3rem" /> von {{ pages.length }} diff --git a/src/app/shared/table/table-pagination/table-pagination.component.ts b/src/app/shared/table/table-pagination/table-pagination.component.ts index 7684b51009..ee67473f06 100644 --- a/src/app/shared/table/table-pagination/table-pagination.component.ts +++ b/src/app/shared/table/table-pagination/table-pagination.component.ts @@ -63,15 +63,15 @@ export class TablePaginationComponent implements OnInit { } /** - * Public method: formatInput. + * Public method: replaceNonNumberInput. * - * It formats the page change input. + * It replaces all non-number input values with empty string. * * @param {HTMLInputElement} input The given input. * - * @returns {void} Formats the input. + * @returns {void} Replaces the input.value. */ - formatInput(input: HTMLInputElement): void { + replaceNonNumberInput(input: HTMLInputElement): void { input.value = input.value.replace(this.FILTER_PAG_REGEX, ''); } From a818bbb3d1313d17c0a9443b017adb4a147fe2a4 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 3 Jul 2022 23:46:51 +0200 Subject: [PATCH 016/122] test(shared): add tests for TablePaginationComponent --- .../table-pagination.component.spec.ts | 346 +++++++++++++++++- 1 file changed, 342 insertions(+), 4 deletions(-) diff --git a/src/app/shared/table/table-pagination/table-pagination.component.spec.ts b/src/app/shared/table/table-pagination/table-pagination.component.spec.ts index 5bae56db1d..2fbf6ce4f4 100644 --- a/src/app/shared/table/table-pagination/table-pagination.component.spec.ts +++ b/src/app/shared/table/table-pagination/table-pagination.component.spec.ts @@ -1,13 +1,30 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NgModule } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { DebugElement, NgModule } from '@angular/core'; -import { NgbConfig, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; +import Spy = jasmine.Spy; +import { NgbConfig, NgbPaginationModule, NgbPagination } from '@ng-bootstrap/ng-bootstrap'; + +import { + expectSpyCall, + getAndExpectDebugElementByCss, + getAndExpectDebugElementByDirective, +} from '@testing/expect-helper'; import { TablePaginationComponent } from './table-pagination.component'; describe('TablePaginationComponent', () => { let component: TablePaginationComponent; let fixture: ComponentFixture; + let compDe: DebugElement; + + let emitPageChangeSpy: Spy; + let emitPageChangeRequestSpy: Spy; + let onPageChangeSpy: Spy; + let replaceNonNumberInputSpy: Spy; + let selectPageSpy: Spy; + + let expectedCollectionSize: number; + let expectedPage: number; // global NgbConfigModule @NgModule({ imports: [NgbPaginationModule], exports: [NgbPaginationModule] }) @@ -28,10 +45,331 @@ describe('TablePaginationComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(TablePaginationComponent); component = fixture.componentInstance; - fixture.detectChanges(); + compDe = fixture.debugElement; + + // Test data + expectedCollectionSize = 100; + expectedPage = 1; + + // Spy on methods + emitPageChangeSpy = spyOn(component.pageChange, 'emit').and.callThrough(); + emitPageChangeRequestSpy = spyOn(component.pageChangeRequest, 'emit').and.callThrough(); + onPageChangeSpy = spyOn(component, 'onPageChange').and.callThrough(); + replaceNonNumberInputSpy = spyOn(component, 'replaceNonNumberInput').and.callThrough(); + selectPageSpy = spyOn(component, 'selectPage').and.callThrough(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('BEFORE initial data binding', () => { + it('should not have collectionSize', () => { + expect(component.collectionSize).toBeUndefined(); + }); + + it('should not have page', () => { + expect(component.page).toBeUndefined(); + }); + + it('should have FILTER_PAG_REGEX', () => { + expect(component.FILTER_PAG_REGEX).toBeDefined(); + + expect(component.FILTER_PAG_REGEX).withContext(`should equal /\\D/g`).toEqual(/\D/g); + }); + + it('should not have called formatInput', () => { + expectSpyCall(replaceNonNumberInputSpy, 0); + }); + + it('should not have called onPageChange', () => { + expectSpyCall(onPageChangeSpy, 0); + }); + + it('should not have called selectPage', () => { + expectSpyCall(selectPageSpy, 0); + }); + + describe('VIEW', () => { + it('should have one ngbPagination component', () => { + getAndExpectDebugElementByDirective(compDe, NgbPagination, 1, 1); + }); + + it('should have one ul in ngbPagination with no content yet', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul', 1, 1); + getAndExpectDebugElementByCss(ulDe[0], 'li.page-item', 0, 0); + }); + }); + }); + + describe('AFTER initial data binding', () => { + beforeEach(() => { + // Simulate the parent setting the input properties + component.collectionSize = expectedCollectionSize; + component.page = expectedPage; + + // Trigger initial data binding + fixture.detectChanges(); + }); + + it('should have collectionSize', () => { + expect(component.collectionSize).toBeDefined(); + expect(component.collectionSize) + .withContext(`should equal ${expectedCollectionSize}`) + .toEqual(expectedCollectionSize); + }); + + it('should have page', () => { + expect(component.page).toBeDefined(); + expect(component.page).withContext(`should equal ${expectedPage}`).toEqual(expectedPage); + }); + + describe('VIEW', () => { + it('should have one ngbPagination component', () => { + getAndExpectDebugElementByDirective(compDe, NgbPagination, 1, 1); + }); + + it('should have one ul.pagination in ngbPagination with 4 li.page-item', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul.pagination', 1, 1); + getAndExpectDebugElementByCss(ulDe[0], 'li.page-item', 4, 4); + }); + + it('should have first two li.page-item with class .disabled', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul.pagination', 1, 1); + + const liDe = getAndExpectDebugElementByCss(ulDe[0], 'li.page-item', 4, 4); + const liEl1 = liDe[0].nativeElement; + const liEl2 = liDe[1].nativeElement; + + expect(liEl1.classList.contains('disabled')).toBeTruthy(); + expect(liEl1).toHaveClass('disabled'); + + expect(liEl2.classList.contains('disabled')).toBeTruthy(); + expect(liEl2).toHaveClass('disabled'); + }); + + it('should have last two li.page-item not with class .disabled', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul.pagination', 1, 1); + + const liDe = getAndExpectDebugElementByCss(ulDe[0], 'li.page-item', 4, 4); + const liEl3 = liDe[2].nativeElement; + const liEl4 = liDe[3].nativeElement; + + expect(liEl3.classList.contains('disabled')).toBeFalsy(); + expect(liEl3).not.toHaveClass('disabled'); + + expect(liEl4.classList.contains('disabled')).toBeFalsy(); + expect(liEl4).not.toHaveClass('disabled'); + }); + + it('should have a.page-link in all li.page-item', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul.pagination', 1, 1); + + const liDe = getAndExpectDebugElementByCss(ulDe[0], 'li.page-item', 4, 4); + + getAndExpectDebugElementByCss(liDe[0], 'a.page-link', 1, 1); + getAndExpectDebugElementByCss(liDe[1], 'a.page-link', 1, 1); + getAndExpectDebugElementByCss(liDe[2], 'a.page-link', 1, 1); + getAndExpectDebugElementByCss(liDe[3], 'a.page-link', 1, 1); + }); + + it('should have one li.ngb-custom-pages-item in ul.pagination', () => { + const ulDe = getAndExpectDebugElementByCss(compDe, 'ngb-pagination > ul.pagination', 1, 1); + + getAndExpectDebugElementByCss(ulDe[0], 'li.ngb-custom-pages-item', 1, 1); + }); + + it('should have one div with label, input and span in li.ngb-custom-page-item', () => { + const liDe = getAndExpectDebugElementByCss(compDe, 'li.ngb-custom-pages-item', 1, 1); + const divDe = getAndExpectDebugElementByCss(liDe[0], 'div', 1, 1); + + getAndExpectDebugElementByCss(divDe[0], 'label#paginationInputLabel', 1, 1); + getAndExpectDebugElementByCss(divDe[0], 'input#paginationInput.custom-pages-input', 1, 1); + getAndExpectDebugElementByCss(divDe[0], 'span#paginationDescription', 1, 1); + }); + + it('should display `Seite` in label', () => { + const expectedLabel = 'Seite'; + + const divDe = getAndExpectDebugElementByCss(compDe, 'li.ngb-custom-pages-item > div', 1, 1); + + const labelDe = getAndExpectDebugElementByCss(divDe[0], 'label#paginationInputLabel', 1, 1); + const labelEl = labelDe[0].nativeElement; + + expect(labelEl.textContent).toBeTruthy(); + expect(labelEl.textContent).withContext(`should be ${expectedLabel}`).toBe(expectedLabel); + }); + + it('should display recent page in input', () => { + const expectedInputValue = expectedPage.toString(); + + const divDe = getAndExpectDebugElementByCss(compDe, 'li.ngb-custom-pages-item > div', 1, 1); + const inputDe = getAndExpectDebugElementByCss( + divDe[0], + 'input#paginationInput.custom-pages-input', + 1, + 1 + ); + const inputEl = inputDe[0].nativeElement; + + expect(inputEl.value).toBeTruthy(); + expect(inputEl.value).withContext(`should be ${expectedInputValue}`).toBe(expectedInputValue); + }); + + it('should display `von pages.length` in span', () => { + const expectedPagesLength = expectedCollectionSize / 10; + const expectedSpanText = `von ${expectedPagesLength}`; + + const divDe = getAndExpectDebugElementByCss(compDe, 'li.ngb-custom-pages-item > div', 1, 1); + + const spanDe = getAndExpectDebugElementByCss(divDe[0], 'span#paginationDescription', 1, 1); + const spanEl = spanDe[0].nativeElement; + + expect(spanEl.textContent).toBeTruthy(); + expect(spanEl.textContent.trim()).withContext(`should be ${expectedSpanText}`).toBe(expectedSpanText); + }); + }); + + describe('#replaceNonNumberInput()', () => { + it('should have a replaceNonNumberInput method', () => { + expect(component.replaceNonNumberInput).toBeDefined(); + }); + + it('should trigger on input event', () => { + const inputDe = getAndExpectDebugElementByCss(compDe, 'input#paginationInput.custom-pages-input', 1, 1); + const inputEl = inputDe[0].nativeElement; + + expectSpyCall(replaceNonNumberInputSpy, 0); + + inputEl.dispatchEvent(new Event('input')); + + expectSpyCall(replaceNonNumberInputSpy, 1); + }); + + describe('should format the HTMLInputElement input', () => { + it('... by keeping numbers', () => { + const input = document.createElement('input'); + input.value = '3'; + + component.replaceNonNumberInput(input); + expect(input.value).toBe('3'); + }); + + it('... by replacing non-numbers with empty string', () => { + const input = document.createElement('input'); + input.value = 'Test'; + + component.replaceNonNumberInput(input); + expect(input.value).toBe(''); + }); + }); + }); + + describe('#onPageChange()', () => { + it('should have a onPageChange method', () => { + expect(component.onPageChange).toBeDefined(); + }); + + it('should trigger on pageChange event of NgbPagination', fakeAsync(() => { + const expectedNewPage = 3; + + const ngbPaginationDe = getAndExpectDebugElementByDirective(compDe, NgbPagination, 1, 1); + const ngbPaginationCmp = ngbPaginationDe[0].injector.get(NgbPagination) as NgbPagination; + + expectSpyCall(onPageChangeSpy, 0); + + ngbPaginationCmp.pageChange.emit(expectedNewPage); + tick(); // Wait for value to be emitted + + expectSpyCall(onPageChangeSpy, 1, expectedNewPage); + })); + + it('should not do anything if newPage is not given', () => { + component.onPageChange(undefined); + + expectSpyCall(emitPageChangeSpy, 0); + expectSpyCall(emitPageChangeRequestSpy, 0); + }); + + it('should emit the newPage', () => { + const expectedNewPage = 3; + + component.onPageChange(expectedNewPage); + + expectSpyCall(emitPageChangeSpy, 1, expectedNewPage); + expectSpyCall(emitPageChangeRequestSpy, 1, expectedNewPage); + }); + }); + + describe('#selectPage()', () => { + it('should have a selectChange method', () => { + expect(component.selectPage).toBeDefined(); + }); + + it('should trigger on blur event', () => { + const inputDe = getAndExpectDebugElementByCss(compDe, 'input#paginationInput.custom-pages-input', 1, 1); + const inputEl = inputDe[0].nativeElement; + + expectSpyCall(selectPageSpy, 0); + + inputEl.value = '5'; + inputEl.dispatchEvent(new Event('blur')); + + expectSpyCall(selectPageSpy, 1, '5'); + }); + + it('should trigger on keyup.enter event', () => { + const inputDe = getAndExpectDebugElementByCss(compDe, 'input#paginationInput.custom-pages-input', 1, 1); + const inputEl = inputDe[0].nativeElement; + + expectSpyCall(selectPageSpy, 0); + + inputEl.value = '5'; + inputEl.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); + + expectSpyCall(selectPageSpy, 1, '5'); + }); + + describe('should set the page', () => { + it('... to the parsed integer value of a given number string', () => { + const expectedNewPage = 3; + + component.selectPage(expectedNewPage.toString()); + + expect(component.page).toBeTruthy(); + expect(component.page).withContext(`should be ${expectedNewPage}`).toBe(expectedNewPage); + }); + + it('... to 1 if the given value cannot be parsed to an integer', () => { + const expectedNewPage = 'Test'; + + component.selectPage(expectedNewPage); + + expect(component.page).toBeTruthy(); + expect(component.page).withContext(`should be 1`).toBe(1); + + const expectedNewPage2 = 'NaN'; + + component.selectPage(expectedNewPage2); + + expect(component.page).toBeTruthy(); + expect(component.page).withContext(`should be 1`).toBe(1); + + const expectedNewPage3 = '_123'; + + component.selectPage(expectedNewPage3); + + expect(component.page).toBeTruthy(); + expect(component.page).withContext(`should be 1`).toBe(1); + }); + + it('... to 1 if the given value is undefined', () => { + component.selectPage(undefined); + + expect(component.page).toBeTruthy(); + expect(component.page).withContext(`should be 1`).toBe(1); + }); + }); + }); + }); }); From b7e9e1f7a3f33bb8c01091d87d6354c8325d60eb Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Mon, 4 Jul 2022 15:34:35 +0200 Subject: [PATCH 017/122] refactor(core): move ToastService to shared ToastComponent --- src/app/core/services/index.ts | 2 -- src/app/core/services/toast-service/index.ts | 1 - src/app/shared/toast/toast.component.spec.ts | 2 +- src/app/shared/toast/toast.component.ts | 2 +- .../toast-service => shared/toast}/toast.service.spec.ts | 0 .../services/toast-service => shared/toast}/toast.service.ts | 0 .../graph-visualizer/graph-visualizer.component.spec.ts | 2 +- .../graph-visualizer/graph-visualizer.component.ts | 2 +- .../sparql-editor/sparql-editor.component.spec.ts | 4 ++-- .../graph-visualizer/sparql-editor/sparql-editor.component.ts | 2 +- .../triples-editor/triples-editor.component.spec.ts | 2 +- .../triples-editor/triples-editor.component.ts | 2 +- 12 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 src/app/core/services/toast-service/index.ts rename src/app/{core/services/toast-service => shared/toast}/toast.service.spec.ts (100%) rename src/app/{core/services/toast-service => shared/toast}/toast.service.ts (100%) diff --git a/src/app/core/services/index.ts b/src/app/core/services/index.ts index 44704324a2..d75c2ad507 100644 --- a/src/app/core/services/index.ts +++ b/src/app/core/services/index.ts @@ -17,7 +17,6 @@ import { HttpCacheService } from './http-cache'; import { LoadingService } from './loading-service'; import { SideInfoService } from './side-info-service'; import { StorageService } from './storage-service'; -import { ToastService } from './toast-service'; export { AnalyticsService, @@ -30,5 +29,4 @@ export { LoadingService, SideInfoService, StorageService, - ToastService, }; diff --git a/src/app/core/services/toast-service/index.ts b/src/app/core/services/toast-service/index.ts deleted file mode 100644 index e78b628f9c..0000000000 --- a/src/app/core/services/toast-service/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './toast.service'; diff --git a/src/app/shared/toast/toast.component.spec.ts b/src/app/shared/toast/toast.component.spec.ts index aa88e4e4de..3a14a53c01 100644 --- a/src/app/shared/toast/toast.component.spec.ts +++ b/src/app/shared/toast/toast.component.spec.ts @@ -3,7 +3,7 @@ import { Component, DebugElement, EventEmitter, Input, Output, TemplateRef, View import { getAndExpectDebugElementByDirective } from '@testing/expect-helper'; -import { Toast, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastService } from './toast.service'; import { ToastComponent } from './toast.component'; // Mock component to get templateRef diff --git a/src/app/shared/toast/toast.component.ts b/src/app/shared/toast/toast.component.ts index af27599168..9cd27666fc 100644 --- a/src/app/shared/toast/toast.component.ts +++ b/src/app/shared/toast/toast.component.ts @@ -1,6 +1,6 @@ import { Component, TemplateRef } from '@angular/core'; -import { Toast, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastService } from './toast.service'; /** * The Toast component. diff --git a/src/app/core/services/toast-service/toast.service.spec.ts b/src/app/shared/toast/toast.service.spec.ts similarity index 100% rename from src/app/core/services/toast-service/toast.service.spec.ts rename to src/app/shared/toast/toast.service.spec.ts diff --git a/src/app/core/services/toast-service/toast.service.ts b/src/app/shared/toast/toast.service.ts similarity index 100% rename from src/app/core/services/toast-service/toast.service.ts rename to src/app/shared/toast/toast.service.ts diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts index 2b7c62e5b9..2b0da6ec76 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts @@ -13,7 +13,7 @@ import { } from '@testing/expect-helper'; import { mockConsole } from '@testing/mock-helper'; -import { Toast, ToastMessage, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastMessage, ToastService } from '@awg-shared/toast/toast.service'; import { GraphRDFData, GraphSparqlQuery } from '@awg-views/edition-view/models'; import { D3SimulationNode, D3SimulationNodeType, Triple } from './models'; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts index 549c89eb74..4a0c350df2 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts @@ -6,7 +6,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChil import { EMPTY, from, Observable } from 'rxjs'; -import { Toast, ToastMessage, ToastService } from '@awg-core/services/toast-service'; +import { Toast, ToastMessage, ToastService } from '@awg-shared/toast/toast.service'; import { GraphSparqlQuery, GraphRDFData } from '@awg-views/edition-view/models'; import { D3SimulationNode, QueryResult, Triple } from './models'; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts index f45d960b8d..3ccee9f0d5 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.spec.ts @@ -19,11 +19,11 @@ import { getAndExpectDebugElementByDirective, } from '@testing/expect-helper'; +import { ToastMessage } from '@awg-shared/toast/toast.service'; import { GraphSparqlQuery } from '@awg-views/edition-view/models'; - import { CmConfig } from '../models'; + import { SparqlEditorComponent } from './sparql-editor.component'; -import { ToastMessage } from '@awg-core/services/toast-service'; // eslint-disable-next-line @angular-eslint/component-selector @Component({ selector: 'ngx-codemirror', template: '' }) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts index 70883e3455..c7c98ab203 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewCh import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; import { faDiagramProject, faTable } from '@fortawesome/free-solid-svg-icons'; -import { ToastMessage } from '@awg-core/services/toast-service'; +import { ToastMessage } from '@awg-shared/toast/toast.service'; import { GraphSparqlQuery } from '@awg-views/edition-view/models'; import { CmConfig } from '../models'; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts index 62fdd087cf..910605df3b 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.spec.ts @@ -12,7 +12,7 @@ import { getAndExpectDebugElementByDirective, } from '@testing/expect-helper'; -import { ToastMessage } from '@awg-core/services/toast-service'; +import { ToastMessage } from '@awg-shared/toast/toast.service'; import { CmConfig } from '../models'; import { TriplesEditorComponent } from './triples-editor.component'; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts index d8c4873490..639489691d 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/triples-editor/triples-editor.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; -import { ToastMessage } from '@awg-core/services/toast-service'; +import { ToastMessage } from '@awg-shared/toast/toast.service'; import { CmConfig } from '../models'; import 'codemirror/mode/turtle/turtle'; From 988bcce95cfc06a1cc3e18448024ebcdd6c304c0 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 5 Jul 2022 13:19:42 +0200 Subject: [PATCH 018/122] refactor(app): use capital letters for prefix enum --- .../force-graph/force-graph.component.ts | 8 ++++---- .../graph-visualizer/models/prefix.model.ts | 4 ++-- .../graph-visualizer/prefix-pipe/prefix.pipe.spec.ts | 12 ++++++------ .../graph-visualizer/prefix-pipe/prefix.pipe.ts | 6 +++--- .../services/graph-visualizer.service.ts | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/force-graph/force-graph.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/force-graph/force-graph.component.ts index 45e0b97674..ebe877837b 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/force-graph/force-graph.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/force-graph/force-graph.component.ts @@ -641,7 +641,7 @@ export class ForceGraphComponent implements OnInit, OnChanges, OnDestroy { // Rdf:type predicateNode.label === 'a' || predicateNode.label === 'rdf:type' || - predicateNode.label === this.prefixPipe.transform('rdf:type', PrefixForm.long) + predicateNode.label === this.prefixPipe.transform('rdf:type', PrefixForm.LONG) ); } @@ -885,9 +885,9 @@ export class ForceGraphComponent implements OnInit, OnChanges, OnDestroy { // Initial Graph from triples triples.forEach((triple: Triple) => { - const subjId = this.prefixPipe.transform(triple.subject, PrefixForm.short); - const predId = this.prefixPipe.transform(triple.predicate, PrefixForm.short); - let objId = this.prefixPipe.transform(triple.object, PrefixForm.short); + const subjId = this.prefixPipe.transform(triple.subject, PrefixForm.SHORT); + const predId = this.prefixPipe.transform(triple.predicate, PrefixForm.SHORT); + let objId = this.prefixPipe.transform(triple.object, PrefixForm.SHORT); // Check if object is number & round decimal numbers to 2 decimals if (!isNaN(objId)) { diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/models/prefix.model.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/models/prefix.model.ts index 6b6bf09514..a3caea3d7d 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/models/prefix.model.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/models/prefix.model.ts @@ -4,8 +4,8 @@ * It stores the possible prefix forms. */ export enum PrefixForm { - short = 'short', - long = 'long', + SHORT = 'short', + LONG = 'long', } /** diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts index 8b72c32bda..d22e18838e 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts @@ -18,7 +18,7 @@ describe('PrefixPipe', () => { const shortForm = 'rdf:'; const longForm = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; - const transformedValue = pipe.transform(shortForm, PrefixForm.long); + const transformedValue = pipe.transform(shortForm, PrefixForm.LONG); expect(transformedValue).toBe(longForm); }); @@ -28,7 +28,7 @@ describe('PrefixPipe', () => { const shortForm = 'rdf:'; const longForm = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; - const transformedValue = pipe.transform(longForm, PrefixForm.short); + const transformedValue = pipe.transform(longForm, PrefixForm.SHORT); expect(transformedValue).toBe(shortForm); }); @@ -37,7 +37,7 @@ describe('PrefixPipe', () => { const pipe = new PrefixPipe(); const shortForm = 'bibo'; - const transformedValue = pipe.transform(shortForm, PrefixForm.long); + const transformedValue = pipe.transform(shortForm, PrefixForm.LONG); expect(transformedValue).toBe(shortForm); }); @@ -46,18 +46,18 @@ describe('PrefixPipe', () => { const pipe = new PrefixPipe(); const longForm = 'http://purl.org/ontology/bibo/'; - const transformedValue = pipe.transform(longForm, PrefixForm.short); + const transformedValue = pipe.transform(longForm, PrefixForm.SHORT); expect(transformedValue).toBe(longForm); }); - it('should throw an error if the prefixForm is not equal to PrefixForm.short or PrefixForm.long', () => { + it('should throw an error if the prefixForm is not equal to PrefixForm.SHORT or PrefixForm.LONG', () => { const pipe = new PrefixPipe(); const shortForm = 'rdf:'; const longForm = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; expect(() => pipe.transform(shortForm, undefined)).toThrowError( - `The prefixForm must be ${PrefixForm.short} or ${PrefixForm.long}, but was: undefined.` + `The prefixForm must be ${PrefixForm.SHORT} or ${PrefixForm.LONG}, but was: undefined.` ); }); }); diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.ts index 7b7e72b432..d2293ee620 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.ts @@ -29,7 +29,7 @@ export class PrefixPipe implements PipeTransform { let transformedValue: any; switch (prefixForm) { - case PrefixForm.short: { + case PrefixForm.SHORT: { this.defaultPrefixes.forEach((p: Prefix) => { if (value.indexOf(p.prefixIri) !== -1) { transformedValue = value.replace(p.prefixIri, p.prefixName + ':'); @@ -37,7 +37,7 @@ export class PrefixPipe implements PipeTransform { }); break; } - case PrefixForm.long: { + case PrefixForm.LONG: { this.defaultPrefixes.forEach((p: Prefix) => { if (value.indexOf(p.prefixName) !== -1) { transformedValue = value.replace(p.prefixName + ':', p.prefixIri); @@ -49,7 +49,7 @@ export class PrefixPipe implements PipeTransform { // This branch should not be reached const exhaustiveCheck: never = prefixForm; throw new Error( - `The prefixForm must be ${PrefixForm.short} or ${PrefixForm.long}, but was: ${exhaustiveCheck}.` + `The prefixForm must be ${PrefixForm.SHORT} or ${PrefixForm.LONG}, but was: ${exhaustiveCheck}.` ); } // If the value was not transformed, return the original value diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/services/graph-visualizer.service.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/services/graph-visualizer.service.ts index 9b6fd0ca5a..08eee97761 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/services/graph-visualizer.service.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/services/graph-visualizer.service.ts @@ -183,7 +183,7 @@ export class GraphVisualizerService { `Prefix '${qName}' not declared in SPARQL and/or Turtle header. Searching in default namespaces...` ); - const defaultNamespace = this.prefixPipe.transform(qName, PrefixForm.long); + const defaultNamespace = this.prefixPipe.transform(qName, PrefixForm.LONG); if (defaultNamespace !== qName) { const missingPrefix = `PREFIX ${qName} <${defaultNamespace}>\n`; missingNamespacesStr += missingPrefix; @@ -588,7 +588,7 @@ export class GraphVisualizerService { if (b[i][varKeys[key]]['value']) { b[i][varKeys[key]]['label'] = this.prefixPipe.transform( b[i][varKeys[key]]['value'], - PrefixForm.short + PrefixForm.SHORT ); // Transform integer values to numbers From 54ee38c3d8604ac8d50259c67519cf42e6baf160 Mon Sep 17 00:00:00 2001 From: Thomas Ahrend Date: Tue, 5 Jul 2022 14:22:29 +0200 Subject: [PATCH 019/122] fix(edition): minor fixes (mainly tabs etc.) in source-decription and source-list of op12 and op25 --- .../edition/series/1/section/5/op12/source-description.json | 2 +- .../data/edition/series/1/section/5/op12/source-list.json | 4 ++-- .../edition/series/1/section/5/op25/source-description.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/assets/data/edition/series/1/section/5/op12/source-description.json b/src/assets/data/edition/series/1/section/5/op12/source-description.json index cfdeb145cd..da3b2c3c27 100644 --- a/src/assets/data/edition/series/1/section/5/op12/source-description.json +++ b/src/assets/data/edition/series/1/section/5/op12/source-description.json @@ -16,7 +16,7 @@ "2 Blätter (Bl. 1–2). Archivalische Paginierung [1] bis [4] unten links (recto) bzw. rechts (verso) mit Bleistift. Bl. 2v mit Ausnahme der archivalischen Paginierung unbeschriftet. Rissspuren am linken und oberen Rand: Blätter von Bogen abgetrennt und im Format verändert.", "Beschreibstoff: Notenpapier, 14 Systeme, Format: quer ca. 160–180 × 267 mm, Firmenzeichen:

auf Bl. 1r unten links (Bl. 1);
Notenpapier, 16 Systeme, Format: quer 175 × 270 mm, kein Firmenzeichen (Bl. 2).", "Schreibstoff: Bleistift.", - "Inhalt:
M 212 Sk1 (Skizze zu „Der Tag ist vergangen“ M 212):
Bl. 1r
System 2–4: T. 1–6;
System 6–9: T. 7–12;
System 10–14: T. 13–17;
Bl. 2r
System 6–10: T. 18–22;
System 12–15: T. 23–24.
M 212 Sk2 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v
System 14–12b (auf dem Kopf stehend): T. 10–12.
M 212 Sk3 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v
System 12a–9a (auf dem Kopf stehend): T. 10–11.
M 212 Sk5 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v
System 9b–6 (auf dem Kopf stehend): T. 10–11.
M 212 Sk4 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v
System 7a–4 (auf dem Kopf stehend): T. 10–11.
Bl. 2v unbeschriftet." + "Inhalt:
M 212 Sk1 (Skizze zu „Der Tag ist vergangen“ M 212):
Bl. 1r System 2–4: T. 1–6;
System 6–9: T. 7–12;
System 10–14: T. 13–17;
Bl. 2r System 6–10: T. 18–22;
System 12–15: T. 23–24.
M 212 Sk2 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v System 14–12b (auf dem Kopf stehend): T. 10–12.
M 212 Sk3 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v System 12a–9a (auf dem Kopf stehend): T. 10–11.
M 212 Sk5 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v System 9b–6 (auf dem Kopf stehend): T. 10–11.
M 212 Sk4 (Skizze zu „Der Tag ist vergangen“ M 212: Textfassung 2):
Bl. 1v System 7a–4 (auf dem Kopf stehend): T. 10–11.
Bl. 2v unbeschriftet." ] }, { diff --git a/src/assets/data/edition/series/1/section/5/op12/source-list.json b/src/assets/data/edition/series/1/section/5/op12/source-list.json index 5b68f67159..e360ab2b91 100644 --- a/src/assets/data/edition/series/1/section/5/op12/source-list.json +++ b/src/assets/data/edition/series/1/section/5/op12/source-list.json @@ -36,7 +36,7 @@ "linkTo": "OP12_SOURCE_NOT_A" }, { - "siglum": "E1", + "siglum": "EH", "type": "Handexemplar von E mit Korrekturen Weberns:
„Der Tag ist vergangen“ M 212: Textfassung 2→3.", "location": "CH-Bps, Sammlung Anton Webern.", "hasDescription": false, @@ -57,7 +57,7 @@ "linkTo": "OP12_SOURCE_NOT_A" }, { - "siglum": "G1", + "siglum": "GH", "type": "Handexemplar von G.", "location": "US-Wc, Moldenhauer Archives, Box-Folder: 59/10.", "hasDescription": false, diff --git a/src/assets/data/edition/series/1/section/5/op25/source-description.json b/src/assets/data/edition/series/1/section/5/op25/source-description.json index 5181536a3c..720b91ba78 100644 --- a/src/assets/data/edition/series/1/section/5/op25/source-description.json +++ b/src/assets/data/edition/series/1/section/5/op25/source-description.json @@ -10,11 +10,11 @@ "Beschreibstoff: Notenpapier, quer 270 × 335 mm, 16 Systeme, Firmenzeichen:

auf Bl. 39r unten links (Bl. 38–39).", "Hauptschreibstoff: Bleistift.", "Weitere Schreibstoffe: grüner Buntstift, roter Buntstift, schwarzer Buntstift.", - "Autographer Titel: „Wie bin ich froh!“ auf Bl. 38v oben links mit schwarzem Buntstift.", + "Titel: „Wie bin ich froh!“ auf Bl. 38v oben links mit schwarzem Buntstift.", "Taktzahlen: Taktzahlen 1–2, 3–6 und 9–15 in M 317 Sk4 sowie 2–3 in M 317 Sk4.1 mit schwarzem Buntstift; Taktzahlen 7–8 in M 317 Sk4 mit Bleistift und nachgezogen mit schwarzem Buntstift.
Taktzahl 52 (zu op. 24/1) auf Bl. 39r oben links mit schwarzem Buntstift.", "Instrumentenvorsatz: Akkoladenstrich und teilweise Schlüsselvorsatz auf Bl. 38v System 1–4 (zu op. 24/1).
Akkoladenstrich und Schlüsselvorsatz auf Bl. 39r System 1–4 (zu op. 24/1), eingekreist und gestrichen mit rotem Buntstift.", "Eintragungen: Reihe auf Bl. 38v System 5 links (zu M 317 Sk3.1.3) mit schwarzem Buntstift.", - "Inhalt:
M 317 Sk2 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1a: Reihenfragment (A) und Zwölftonreihenform Gcis (B).
M 317 Sk1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1b: T. 1–2.
M 317 Sk2.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 1c–3c: T. 1–4.
M 317 Sk2.1.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 1d: T. 1–2.
M 317 Sk2.1.2 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 2a: T. 3.
M 317 Sk2.1.2.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 4c: T. 3.
M 317 Sk2.1.3 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 3d–4d: Tonbuchstaben.
M 317 Sk3 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 3a: T. 1–5.
M 317 Sk3.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 3b–5b: T. 2–4, {{ '{' }}5–6{{ '}' }}.
M 317 Sk3.1.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 5c–6b: T. 4–5.
M 317 Sk3.1.2 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 4a: T. 3–5.
M 317 Sk3.1.3 (Reihentabelle zu „Wie bin ich froh!“ M 317):
Bl. 38v
System 6a: Gg (1); System 6b: Kgis (2);
System 7a: Ug (3); System 7b: KUfis (4).
M 317 Sk3.1.3.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 6c: Zwölftonreihenform Gg.
M 317 Sk4:
Bl. 38v System 10–12: T. {{ '{' }}1A{{ '}' }}, {{ '{' }}1B{{ '}' }}, T. 1–2, 3A;
Bl. 39r System 1–8b: T. 3B, 4–6;
System 9–12, 13c, 14b: T. 7–12;
System 13a–16a: T. 13–15.
M 317 Sk4.1 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 38v System 13–15: T. 2,{{ '{' }}3{{ '}' }}.
M 317 Sk4.2 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 39r System 13b: T. 13.
M 317 Sk4.3 (Skizze zu „Wie bin ich froh!“ M 317): Bl. 39r System 15c–16c: T. 13." + "Inhalt:
M 317 Sk2 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1a: Reihenfragment (A) und Zwölftonreihenform Gcis (B).
M 317 Sk1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1b: T. 1–2.
M 317 Sk2.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1c–3c: T. 1–4.
M 317 Sk2.1.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 1d: T. 1–2.
M 317 Sk2.1.2 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 2a: T. 3.
M 317 Sk2.1.2.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 4c: T. 3.
M 317 Sk2.1.3 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 3d–4d: Tonbuchstaben.
M 317 Sk3 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 3a: T. 1–5.
M 317 Sk3.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 3b–5b: T. 2–4, {{ '{' }}5–6{{ '}' }}.
M 317 Sk3.1.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 5c–6b: T. 4–5.
M 317 Sk3.1.2 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 4a: T. 3–5.
M 317 Sk3.1.3 (Reihentabelle zu „Wie bin ich froh!“ M 317):
Bl. 38v
System 6a: Gg (1); System 6b: Kgis (2);
System 7a: Ug (3); System 7b: KUfis (4).
M 317 Sk3.1.3.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 6c: Zwölftonreihenform Gg.
M 317 Sk4 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 10–12: T. {{ '{' }}1A{{ '}' }}, {{ '{' }}1B{{ '}' }}, T. 1–2, 3A;
Bl. 39r System 1–8b: T. 3B, 4–6;
System 9–12, 13c, 14b: T. 7–12;
System 13a–16a: T. 13–15.
M 317 Sk4.1 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 38v System 13–15: T. 2, {{ '{' }}3{{ '}' }}.
M 317 Sk4.2 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 39r System 13b: T. 13.
M 317 Sk4.3 (Skizze zu „Wie bin ich froh!“ M 317):
Bl. 39r System 15c–16c: T. 13." ] }, { @@ -28,7 +28,7 @@ "Schreibstoff: Bleistift; blauer Buntstift, grüner Buntstift, roter Buntstift.", "Titel: Reihen op. 25 [teilweise eingerahmt] auf Bl. 1r oben rechts mit Bleistift, Einrahmung mit blauem Buntstift; 3 Lieder auf Bl. 1r System 1–2 rechts mit rotem Buntstift.", "Datierung: 1934 auf Bl. 1r System 3–4 rechts mit rotem Buntstift.", - "Inhalt:
M 317 Sk5 / M321 Sk1 / M 322 Sk1 (Reihentabelle op. 25):
Bl. 1r
System 1a: Gg (1); System 1b: Kgis (2);
System 2a: Ug (3); System 2b: KUfis (4);
System 3a: Gc (5); System 3b: Kcis (6);
System 4a: Uc (7); System 4b: KUh (8)." + "Inhalt:
M 317 Sk5 / M321 Sk1 / M 322 Sk1 (Reihentabelle op. 25):
Bl. 1r
System 1a: Gg (1); System 1b: Kgis (2);
System 2a: Ug (3); System 2b: KUfis (4);
System 3a: Gc (5); System 3b: Kcis (6);
System 4a: Uc (7); System 4b: KUh (8)." ] }, { From 376aae4e24e4934c63c11d7800b4768a1081c472 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 5 Jul 2022 17:53:53 +0200 Subject: [PATCH 020/122] fix(app): remove unused variables --- src/app/shared/compile-html/compile-html.component.ts | 4 +--- .../graph-visualizer/prefix-pipe/prefix.pipe.spec.ts | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/shared/compile-html/compile-html.component.ts b/src/app/shared/compile-html/compile-html.component.ts index b2b7b12bc9..81ab809c0a 100644 --- a/src/app/shared/compile-html/compile-html.component.ts +++ b/src/app/shared/compile-html/compile-html.component.ts @@ -171,10 +171,8 @@ export class CompileHtmlComponent implements OnChanges { * Angular life cycle hook: ngOnChanges. * * It checks for changes of the given input. - * - * @param {SimpleChanges} changes The changes of the input. */ - ngOnChanges(changes: SimpleChanges) { + ngOnChanges() { this.update(); } diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts index d22e18838e..78214d3398 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/prefix-pipe/prefix.pipe.spec.ts @@ -54,7 +54,6 @@ describe('PrefixPipe', () => { it('should throw an error if the prefixForm is not equal to PrefixForm.SHORT or PrefixForm.LONG', () => { const pipe = new PrefixPipe(); const shortForm = 'rdf:'; - const longForm = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; expect(() => pipe.transform(shortForm, undefined)).toThrowError( `The prefixForm must be ${PrefixForm.SHORT} or ${PrefixForm.LONG}, but was: undefined.` From 78a6c0ce708bcac2f320f62f154a90a0fa1c68bf Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 5 Jul 2022 18:03:52 +0200 Subject: [PATCH 021/122] feat(shared): add ViewHandleButtonGroup --- src/app/shared/shared.module.ts | 3 + .../view-handle-button-group.component.html | 22 +++ .../view-handle-button-group.component.scss | 0 ...view-handle-button-group.component.spec.ts | 27 +++ .../view-handle-button-group.component.ts | 177 ++++++++++++++++++ .../view-handle.model.ts | 50 +++++ 6 files changed, 279 insertions(+) create mode 100644 src/app/shared/view-handle-button-group/view-handle-button-group.component.html create mode 100644 src/app/shared/view-handle-button-group/view-handle-button-group.component.scss create mode 100644 src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts create mode 100644 src/app/shared/view-handle-button-group/view-handle-button-group.component.ts create mode 100644 src/app/shared/view-handle-button-group/view-handle.model.ts diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 36d12695ff..effba8ab27 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -25,6 +25,7 @@ import { TableComponent } from './table/table.component'; import { TablePaginationComponent } from './table/table-pagination/table-pagination.component'; import { ToastComponent } from './toast/toast.component'; import { TwelveToneSpinnerComponent } from './twelve-tone-spinner/twelve-tone-spinner.component'; +import { ViewHandleButtonGroupComponent } from './view-handle-button-group/view-handle-button-group.component'; // // Shared directives @@ -63,6 +64,7 @@ import { ExternalLinkDirective } from './external-link/external-link.directive'; TablePaginationComponent, ToastComponent, TwelveToneSpinnerComponent, + ViewHandleButtonGroupComponent, ExternalLinkDirective, ], exports: [ @@ -87,6 +89,7 @@ import { ExternalLinkDirective } from './external-link/external-link.directive'; TableComponent, ToastComponent, TwelveToneSpinnerComponent, + ViewHandleButtonGroupComponent, ExternalLinkDirective, ], }) diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.html b/src/app/shared/view-handle-button-group/view-handle-button-group.component.html new file mode 100644 index 0000000000..5178c38a1e --- /dev/null +++ b/src/app/shared/view-handle-button-group/view-handle-button-group.component.html @@ -0,0 +1,22 @@ + +
+
+
+ + + + +
+
+
diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.scss b/src/app/shared/view-handle-button-group/view-handle-button-group.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts b/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts new file mode 100644 index 0000000000..c91d95fe69 --- /dev/null +++ b/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; + +import { ViewHandleButtonGroupComponent } from './view-handle-button-group.component'; + +describe('ViewHandleButtonGroupComponent', () => { + let component: ViewHandleButtonGroupComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule], + declarations: [ViewHandleButtonGroupComponent], + providers: [FormBuilder], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewHandleButtonGroupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts b/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts new file mode 100644 index 0000000000..87873aed71 --- /dev/null +++ b/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts @@ -0,0 +1,177 @@ +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; + +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { ViewHandle, ViewHandleTypes } from './view-handle.model'; + +/** + * The ViewHandleButtonGroup component. + * + * It contains the view handle button group that is + * provided via the {@link SharedModule}. + */ +@Component({ + selector: 'awg-view-handle-button-group', + templateUrl: './view-handle-button-group.component.html', + styleUrls: ['./view-handle-button-group.component.scss'], +}) +export class ViewHandleButtonGroupComponent implements OnInit, OnChanges, OnDestroy { + /** + * Input variable: viewHandles. + * + * It keeps the list of view handles. + */ + @Input() + viewHandles: ViewHandle[]; + + /** + * Input variable: selectedViewType. + * + * It keeps the selected view type. + */ + @Input() + selectedViewType: ViewHandleTypes; + + /** + * Output variable: viewChangeRequest. + * + * It keeps an event emitter to inform about the switched view type. + */ + @Output() + viewChangeRequest: EventEmitter = new EventEmitter(); + + /** + * Public variable: viewHandleControlForm. + * + * It keeps the reactive form group for the view handle. + */ + viewHandleControlForm: FormGroup; + + /** + * Private variable: _destroyed$. + * + * Subject to emit a truthy value in the ngOnDestroy lifecycle hook. + */ + private _destroyed$: Subject = new Subject(); + + /** + * Constructor of the ViewHandleButtonGroupComponent. + * + * It declares private instances of the Angular FormBuilder. + * + * @param {FormBuilder} formBuilder Instance of the FormBuilder. + */ + constructor(private formBuilder: FormBuilder) {} + + /** + * Getter for the view handle control value. + */ + get viewHandleControl(): FormControl { + return this.viewHandleControlForm.get('viewHandleControl') as FormControl; + } + + /** + * Angular life cycle hook: ngOnInit. + * + * It calls the containing methods + * when initializing the component. + */ + ngOnInit(): void { + console.log('ViewHandleButtonGroupComponent got view', this.selectedViewType); + this.createFormGroup(this.selectedViewType); + } + + /** + * Angular life cycle hook: ngOnChanges. + * + * It checks for changes of the given input. + * + * @param {SimpleChanges} changes The changes of the input. + */ + ngOnChanges(changes: SimpleChanges) { + if (changes['selectedViewType'] && !changes['selectedViewType'].isFirstChange()) { + this.createFormGroup(this.selectedViewType); + } + } + + /** + * Public method: createFormGroup. + * + * It creates the view handle control form + * using the reactive FormBuilder with a formGroup + * and a view handle control. + * + * @param {ViewHandleTypes} view The given view type. + * + * @returns {void} Creates the view handle control form. + */ + createFormGroup(view: ViewHandleTypes): void { + this.viewHandleControlForm = this.formBuilder.group({ + viewHandleControl: ViewHandleTypes[view], + }); + + this.listenToUserInputChange(); + } + + /** + * Public method: listenToUserInputChange. + * + * It listens to the user's input changes + * in the view control and triggers the + * onViewChange method with the new view type. + * + * @returns {void} Listens to changing view type. + */ + listenToUserInputChange(): void { + this.viewHandleControl.valueChanges.pipe(takeUntil(this._destroyed$)).subscribe({ + next: (view: ViewHandleTypes) => { + this.onViewChange(view); + }, + }); + } + + /** + * Public method: onViewChange. + * + * It switches the view handle type according to the given view type and + * emits a request to the outer components. + * + * @param {ViewHandleTypes} view The given view type. + * + * @returns {void} Emits the view to the view change request. + */ + onViewChange(view: ViewHandleTypes): void { + if (!view) { + return; + } + this.viewChangeRequest.emit(view); + } + + /** + * Public method: viewHandleTracker. + * + * It returns the tracker for the view handle button group. + * + * @param {number} _index The index of the view handle. + * @param {ViewHandle} viewHandle The given view handle. + */ + viewHandleTracker(_index, viewHandle) { + return viewHandle.type; + } + + /** + * Angular life cycle hook: ngOnDestroy. + * + * It calls the containing methods + * when destroying the component. + */ + ngOnDestroy() { + // Emit truthy value to end all subscriptions + this._destroyed$.next(true); + + // Now let's also complete the subject itself + this._destroyed$.complete(); + } +} diff --git a/src/app/shared/view-handle-button-group/view-handle.model.ts b/src/app/shared/view-handle-button-group/view-handle.model.ts new file mode 100644 index 0000000000..786ed30cb7 --- /dev/null +++ b/src/app/shared/view-handle-button-group/view-handle.model.ts @@ -0,0 +1,50 @@ +import { IconDefinition } from '@fortawesome/free-solid-svg-icons'; + +/** + * The ViewHandleTypes enumeration. + * + * It stores the possible types for the view handle button group. + */ +export enum ViewHandleTypes { + GRAPH = 'graph', + GRID = 'grid', + TABLE = 'table', +} + +/** + * The ViewHandle class. + * + * It represents the view handle button group elements. + * It is used to switch between the different view types. + * + */ +export class ViewHandle { + /** + * The label of the view handle. + */ + label: string; + + /** + * The type of the view handle. + */ + type: ViewHandleTypes; + + /** + * The icon of the view handle. + */ + icon: IconDefinition; + + /** + * Constructor of the ViewHandle. + * + * @param {string} label The label of the view handle. + * @param {ViewHandleTypes} type The type of the view handle. + * @param {IconDefinition} icon The icon of the view handle. + * + */ + constructor(label: string, type: ViewHandleTypes, icon: IconDefinition) { + this.label = label; + this.type = type; + this.icon = icon; + } +} From 47cfd449fa51afc3644cc49568b740caaeb1c3cf Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Tue, 5 Jul 2022 18:07:31 +0200 Subject: [PATCH 022/122] fix(edition): use ViewHandleButtonGroup in SparqlComponent --- .../graph-visualizer.component.ts | 3 +- .../sparql-editor.component.html | 6 + .../sparql-editor.component.spec.ts | 1253 +++++++++++------ .../sparql-editor/sparql-editor.component.ts | 139 +- 4 files changed, 945 insertions(+), 456 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts index 4a0c350df2..d7befd55f2 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts @@ -143,9 +143,10 @@ export class GraphVisualizerComponent implements OnInit { } this.queryList = JSON.parse(JSON.stringify(this.graphRDFInputData.queryList)); - this.query = query + const resetted = query ? this.queryList.find(q => query.queryLabel === q.queryLabel && query.queryType === q.queryType) || query : this.queryList[0]; + this.query = { ...resetted }; this.performQuery(); } diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html index 490214e02c..4b759ea185 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.html @@ -7,6 +7,12 @@
+ + +
= new EventEmitter(); } +@Component({ selector: 'awg-view-handle-button-group', template: '' }) +class ViewHandleButtongGroupStubComponent { + @Input() + viewHandles: ViewHandle[]; + @Input() + selectedViewType: ViewHandleTypes; + @Output() + viewChangeRequest: EventEmitter = new EventEmitter(); +} + describe('SparqlEditorComponent (DONE)', () => { let component: SparqlEditorComponent; let fixture: ComponentFixture; let compDe: DebugElement; - let expectedQuery1: GraphSparqlQuery; - let expectedQuery2: GraphSparqlQuery; + let expectedConstructQuery1: GraphSparqlQuery; + let expectedConstructQuery2: GraphSparqlQuery; + let expectedSelectQuery1: GraphSparqlQuery; + let expectedSelectQuery2: GraphSparqlQuery; let expectedQueryList: GraphSparqlQuery[]; let expectedCmSparqlConfig: CmConfig; let expectedIsFullscreen: boolean; + let expectedViewHandles: ViewHandle[]; let isExampleQueriesEnabledSpy: Spy; let onEditorInputChangeSpy: Spy; let onQueryListChangeSpy: Spy; + let onViewChangeSpy: Spy; let performQuerySpy: Spy; let preventPanelCollapseOnFullscreenSpy: Spy; let resetQuerySpy: Spy; + let setViewTypeSpy: Spy; + let switchQueryTypeSpy: Spy; let emitErrorMessageRequestSpy: Spy; let emitPerformQueryRequestSpy: Spy; let emitResestQueryRequestSpy: Spy; @@ -69,7 +86,13 @@ describe('SparqlEditorComponent (DONE)', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [NgbAnimationConfigModule], - declarations: [SparqlEditorComponent, CodeMirrorStubComponent, NgbAccordion, NgbDropdown], + declarations: [ + SparqlEditorComponent, + CodeMirrorStubComponent, + ViewHandleButtongGroupStubComponent, + NgbAccordion, + NgbDropdown, + ], }).compileComponents(); })); @@ -79,17 +102,32 @@ describe('SparqlEditorComponent (DONE)', () => { compDe = fixture.debugElement; // Test data - expectedQuery1 = new GraphSparqlQuery(); - expectedQuery1.queryType = 'select'; - expectedQuery1.queryLabel = 'Test Query 1'; - expectedQuery1.queryString = 'SELECT * WHERE { ?test ?has ?success }'; - - expectedQuery2 = new GraphSparqlQuery(); - expectedQuery2.queryType = 'select'; - expectedQuery2.queryLabel = 'Test Query 2'; - expectedQuery2.queryString = 'SELECT * WHERE { ?success a ?test }'; - - expectedQueryList = [expectedQuery1, expectedQuery2]; + expectedConstructQuery1 = new GraphSparqlQuery(); + expectedConstructQuery1.queryType = 'construct'; + expectedConstructQuery1.queryLabel = 'Test Query 3'; + expectedConstructQuery1.queryString = 'CONSTRUCT WHERE { ?test ?has ?success }'; + + expectedConstructQuery2 = new GraphSparqlQuery(); + expectedConstructQuery2.queryType = 'construct'; + expectedConstructQuery2.queryLabel = 'Test Query 4'; + expectedConstructQuery2.queryString = 'CONSTRUCT WHERE { ?success a ?test }'; + + expectedSelectQuery1 = new GraphSparqlQuery(); + expectedSelectQuery1.queryType = 'select'; + expectedSelectQuery1.queryLabel = 'Test Query 1'; + expectedSelectQuery1.queryString = 'SELECT * WHERE { ?test ?has ?success }'; + + expectedSelectQuery2 = new GraphSparqlQuery(); + expectedSelectQuery2.queryType = 'select'; + expectedSelectQuery2.queryLabel = 'Test Query 2'; + expectedSelectQuery2.queryString = 'SELECT * WHERE { ?success a ?test }'; + + expectedQueryList = [ + expectedConstructQuery1, + expectedConstructQuery2, + expectedSelectQuery1, + expectedSelectQuery2, + ]; expectedIsFullscreen = false; @@ -101,15 +139,23 @@ describe('SparqlEditorComponent (DONE)', () => { mode: 'sparql', }; + expectedViewHandles = [ + new ViewHandle('Graph view', ViewHandleTypes.GRAPH, component.faDiagramProject), + new ViewHandle('Table view', ViewHandleTypes.TABLE, component.faTable), + ]; + // Spies on component functions // `.and.callThrough` will track the spy down the nested describes, see // https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3Eand.callThrough%3C/code%3E isExampleQueriesEnabledSpy = spyOn(component, 'isExampleQueriesEnabled').and.callThrough(); onEditorInputChangeSpy = spyOn(component, 'onEditorInputChange').and.callThrough(); onQueryListChangeSpy = spyOn(component, 'onQueryListChange').and.callThrough(); + onViewChangeSpy = spyOn(component, 'onViewChange').and.callThrough(); performQuerySpy = spyOn(component, 'performQuery').and.callThrough(); preventPanelCollapseOnFullscreenSpy = spyOn(component, 'preventPanelCollapseOnFullscreen').and.callThrough(); resetQuerySpy = spyOn(component, 'resetQuery').and.callThrough(); + setViewTypeSpy = spyOn(component, 'setViewType').and.callThrough(); + switchQueryTypeSpy = spyOn(component, 'switchQueryType').and.callThrough(); emitErrorMessageRequestSpy = spyOn(component.errorMessageRequest, 'emit').and.callThrough(); emitPerformQueryRequestSpy = spyOn(component.performQueryRequest, 'emit').and.callThrough(); emitResestQueryRequestSpy = spyOn(component.resetQueryRequest, 'emit').and.callThrough(); @@ -140,6 +186,20 @@ describe('SparqlEditorComponent (DONE)', () => { .toEqual(expectedCmSparqlConfig); }); + it('should have selectedViewType', () => { + expect(component.selectedViewType).toBeDefined(); + expect(component.selectedViewType) + .withContext(`should equal ${ViewHandleTypes.GRAPH}`) + .toEqual(ViewHandleTypes.GRAPH); + }); + + it('should have viewHandles', () => { + expect(component.viewHandles).toBeDefined(); + expect(component.viewHandles) + .withContext(`should equal ${expectedViewHandles}`) + .toEqual(expectedViewHandles); + }); + describe('VIEW', () => { it('... should contain one ngb-accordion without panel (div.accordion-item) yet', () => { // Ngb-accordion debug element @@ -154,7 +214,7 @@ describe('SparqlEditorComponent (DONE)', () => { describe('AFTER initial data binding', () => { beforeEach(() => { // Simulate the parent setting the input properties - component.query = expectedQuery1; + component.query = expectedConstructQuery1; component.queryList = expectedQueryList; component.isFullscreen = expectedIsFullscreen; @@ -169,7 +229,9 @@ describe('SparqlEditorComponent (DONE)', () => { it('should have `query` input', () => { expect(component.query).toBeDefined(); - expect(component.query).withContext(`should equal ${expectedQuery1}`).toEqual(expectedQuery1); + expect(component.query) + .withContext(`should equal ${expectedConstructQuery1}`) + .toEqual(expectedConstructQuery1); }); it('should have `isFullScreen` input', () => { @@ -220,7 +282,7 @@ describe('SparqlEditorComponent (DONE)', () => { expect(btnEl.textContent).withContext('should be SPARQL').toContain('SPARQL'); }); - it('... should toggle panel body on click', async () => { + it('... should toggle panel body by click on panel header button', async () => { // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( compDe, @@ -275,237 +337,286 @@ describe('SparqlEditorComponent (DONE)', () => { ); }); - it('... should contain an example query btn-group in panel header if isExampleQueriesEnabled = true', async () => { - isExampleQueriesEnabledSpy.and.returnValue(true); - - await detectChangesOnPush(fixture); - - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - // Panel header div.awg-example-query-btn-group - getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 1, - 1 - ); - }); - - it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { - isExampleQueriesEnabledSpy.and.returnValue(false); - - await detectChangesOnPush(fixture); - - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - // Panel header div.awg-example-query-btn-group - getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 0, - 0 - ); - }); - - it('... should display a disabled button label in example query btn-group', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - const btnDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > button.btn', - 1, - 1 - ); - const btnEl = btnDes[0].nativeElement; - - expect(btnEl.disabled).toBeTruthy(); - expect(btnEl.getAttribute('aria-disabled')).toBe('true'); - - expect(btnEl.textContent).toBeTruthy(); - expect(btnEl.textContent).toContain('Beispielabfragen'); - }); - - it('... should contain another btn-group dropdown in example query btn-group', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - const outerButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 1, - 1 - ); - - getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); - }); - - it('... should contain toggle button in example query btn-group dropdown', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); - - getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'button.btn.dropdown-toggle', 1, 1); - }); - - it('... should contain dropdown menu div with dropdown item links in example query btn-group dropdown', fakeAsync(() => { - tick(); - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); - - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); - - const menuDes = getAndExpectDebugElementByCss( - dropdownButtonGroupDes[0], - 'div.dropdown-menu', - 1, - 1 - ); - - getAndExpectDebugElementByCss( - menuDes[0], - 'a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); - })); - - it('... should display label on dropdown items in example query btn-group', () => { - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - compDe, - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); - - const itemDes = getAndExpectDebugElementByCss( - dropdownButtonGroupDes[0], - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); - - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); - - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; - - expect(itemEl0.textContent).toBeTruthy(); - expect(itemEl0.textContent) - .withContext(`should contain ${expectedQueryList[0].queryLabel}`) - .toContain(expectedQueryList[0].queryLabel); - - expect(itemEl1.textContent).toBeTruthy(); - expect(itemEl1.textContent) - .withContext(`should contain ${expectedQueryList[1].queryLabel}`) - .toContain(expectedQueryList[1].queryLabel); - }); - - it('... should disable current query in dropdown items', async () => { - const itemDes = getAndExpectDebugElementByCss( - compDe, - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); - - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); - - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; - - expect(itemEl0).toHaveClass('disabled'); - expect(itemEl1).not.toHaveClass('disabled'); - - component.query = expectedQuery2; - await detectChangesOnPush(fixture); - - expect(itemEl0).not.toHaveClass('disabled'); - expect(itemEl1).toHaveClass('disabled'); + describe('View handle button group', () => { + it('... should contain ViewHandleButtongGroupComponent (stubbed) in panel header', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + getAndExpectDebugElementByDirective( + panelHeaderDes[0], + ViewHandleButtongGroupStubComponent, + 1, + 1 + ); + }); + + it('... should have selectedViewType===graph (according to querytype)', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + // ViewHandleButtongGroupComponent debug elements + const viewHandleButtongGroupDes = getAndExpectDebugElementByDirective( + panelHeaderDes[0], + ViewHandleButtongGroupStubComponent, + 1, + 1 + ); + const viewHandleButtongGroupCmp = viewHandleButtongGroupDes[0].injector.get( + ViewHandleButtongGroupStubComponent + ) as ViewHandleButtongGroupStubComponent; + + expect(viewHandleButtongGroupCmp.selectedViewType).toBeTruthy(); + expect(viewHandleButtongGroupCmp.selectedViewType).toBe(ViewHandleTypes.GRAPH); + }); }); - it('... should trigger `onQueryListChange()` by click on dropdown item', async () => { - const itemDes = getAndExpectDebugElementByCss( - compDe, - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); - - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); - - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; - - expect(itemEl1).not.toHaveClass('disabled'); - - // Click on second item (first disabled) - click(itemEl1 as HTMLElement); - await detectChangesOnPush(fixture); - - // Spy call with second query - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery2); - - component.query = expectedQuery2; - await detectChangesOnPush(fixture); - - expect(itemEl0).not.toHaveClass('disabled'); - - // Click on first item (second disabled) - click(itemEl0 as HTMLElement); - await detectChangesOnPush(fixture); - - // Spy call with first query - expectSpyCall(onQueryListChangeSpy, 2, expectedQuery1); + describe('Example query button group', () => { + it('... should contain an example query btn-group in panel header if isExampleQueriesEnabled = true', async () => { + isExampleQueriesEnabledSpy.and.returnValue(true); + + await detectChangesOnPush(fixture); + + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + // Panel header div.awg-example-query-btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); + }); + + it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { + isExampleQueriesEnabledSpy.and.returnValue(false); + + await detectChangesOnPush(fixture); + + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + // Panel header div.awg-example-query-btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 0, + 0 + ); + }); + + it('... should display a disabled button label in example query btn-group', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + const btnDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > button.btn', + 1, + 1 + ); + const btnEl = btnDes[0].nativeElement; + + expect(btnEl.disabled).toBeTruthy(); + expect(btnEl.getAttribute('aria-disabled')).toBe('true'); + + expect(btnEl.textContent).toBeTruthy(); + expect(btnEl.textContent).toContain('Beispielabfragen'); + }); + + it('... should contain another btn-group dropdown in example query btn-group', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + const outerButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); + + getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); + }); + + it('... should contain toggle button in example query btn-group dropdown', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); + + getAndExpectDebugElementByCss( + dropdownButtonGroupDes[0], + 'button.btn.dropdown-toggle', + 1, + 1 + ); + }); + + it('... should contain dropdown menu div with dropdown item links in example query btn-group dropdown', fakeAsync(() => { + tick(); + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); + + const menuDes = getAndExpectDebugElementByCss( + dropdownButtonGroupDes[0], + 'div.dropdown-menu', + 1, + 1 + ); + + getAndExpectDebugElementByCss( + menuDes[0], + 'a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); + })); + + it('... should display label on dropdown items in example query btn-group', () => { + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + compDe, + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); + + const itemDes = getAndExpectDebugElementByCss( + dropdownButtonGroupDes[0], + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); + + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); + + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; + + expect(itemEl0.textContent).toBeTruthy(); + expect(itemEl0.textContent) + .withContext(`should contain ${expectedQueryList[0].queryLabel}`) + .toContain(expectedQueryList[0].queryLabel); + + expect(itemEl1.textContent).toBeTruthy(); + expect(itemEl1.textContent) + .withContext(`should contain ${expectedQueryList[1].queryLabel}`) + .toContain(expectedQueryList[1].queryLabel); + }); + + it('... should disable current query in dropdown items', async () => { + const itemDes = getAndExpectDebugElementByCss( + compDe, + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); + + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); + + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; + + expect(itemEl0).toHaveClass('disabled'); + expect(itemEl1).not.toHaveClass('disabled'); + + component.query = expectedConstructQuery2; + await detectChangesOnPush(fixture); + + expect(itemEl0).not.toHaveClass('disabled'); + expect(itemEl1).toHaveClass('disabled'); + }); + + it('... should trigger `onQueryListChange()` by click on dropdown item', async () => { + const itemDes = getAndExpectDebugElementByCss( + compDe, + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); + + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); + + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; + + expect(itemEl1).not.toHaveClass('disabled'); + + // Click on second item (first disabled) + click(itemEl1 as HTMLElement); + await detectChangesOnPush(fixture); + + // Spy call with second query + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery2); + + component.query = expectedConstructQuery2; + await detectChangesOnPush(fixture); + + expect(itemEl0).not.toHaveClass('disabled'); + + // Click on first item (second disabled) + click(itemEl0 as HTMLElement); + await detectChangesOnPush(fixture); + + // Spy call with first query + expectSpyCall(onQueryListChangeSpy, 2, expectedConstructQuery1); + }); }); }); @@ -700,7 +811,7 @@ describe('SparqlEditorComponent (DONE)', () => { expect(btnEl.textContent).withContext(`should be 'SPARQL'`).toContain('SPARQL'); }); - it('... should not toggle panel body on click', async () => { + it('... should not toggle panel body by click on panel header button', async () => { // Header debug elements const panelHeaderDes = getAndExpectDebugElementByCss( compDe, @@ -742,231 +853,280 @@ describe('SparqlEditorComponent (DONE)', () => { ); }); - it('... should contain an example query btn-group in panel header if isExampleQueriesEnabled = true', async () => { - isExampleQueriesEnabledSpy.and.returnValue(true); + describe('View handle button group', () => { + it('... should contain ViewHandleButtongGroupComponent (stubbed) in panel header', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - await detectChangesOnPush(fixture); + getAndExpectDebugElementByDirective( + panelHeaderDes[0], + ViewHandleButtongGroupStubComponent, + 1, + 1 + ); + }); - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + it('... should have selectedViewType===graph (according to querytype)', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + // ViewHandleButtongGroupComponent debug elements + const viewHandleButtongGroupDes = getAndExpectDebugElementByDirective( + panelHeaderDes[0], + ViewHandleButtongGroupStubComponent, + 1, + 1 + ); + const viewHandleButtongGroupCmp = viewHandleButtongGroupDes[0].injector.get( + ViewHandleButtongGroupStubComponent + ) as ViewHandleButtongGroupStubComponent; - // Panel header div.btn-group - getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 1, - 1 - ); + expect(viewHandleButtongGroupCmp.selectedViewType).toBeTruthy(); + expect(viewHandleButtongGroupCmp.selectedViewType).toBe(ViewHandleTypes.GRAPH); + }); }); - it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { - isExampleQueriesEnabledSpy.and.returnValue(false); + describe('Example query button group', () => { + it('... should contain an example query btn-group in panel header if isExampleQueriesEnabled = true', async () => { + isExampleQueriesEnabledSpy.and.returnValue(true); - await detectChangesOnPush(fixture); + await detectChangesOnPush(fixture); - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - // Panel header div.btn-group - getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 0, - 0 - ); - }); + // Panel header div.btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); + }); - it('... should display a disabled button label in example query btn-group', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + it('... should not contain an example query btn-group in panel header if isExampleQueriesEnabled = false', async () => { + isExampleQueriesEnabledSpy.and.returnValue(false); - const btnDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > button.btn', - 1, - 1 - ); - const btnEl = btnDes[0].nativeElement; + await detectChangesOnPush(fixture); - expect(btnEl.disabled).toBeTruthy(); - expect(btnEl.getAttribute('aria-disabled')).toBe('true'); + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - expect(btnEl.textContent).toBeTruthy(); - expect(btnEl.textContent).toContain('Beispielabfragen'); - }); + // Panel header div.btn-group + getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 0, + 0 + ); + }); + + it('... should display a disabled button label in example query btn-group', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + const btnDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > button.btn', + 1, + 1 + ); + const btnEl = btnDes[0].nativeElement; + + expect(btnEl.disabled).toBeTruthy(); + expect(btnEl.getAttribute('aria-disabled')).toBe('true'); + + expect(btnEl.textContent).toBeTruthy(); + expect(btnEl.textContent).toContain('Beispielabfragen'); + }); - it('... should contain another btn-group dropdown in example query btn-group', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + it('... should contain another btn-group dropdown in example query btn-group', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - const outerButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group', - 1, - 1 - ); + const outerButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group', + 1, + 1 + ); - getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); - }); + getAndExpectDebugElementByCss(outerButtonGroupDes[0], 'div.btn-group.dropdown', 1, 1); + }); - it('... should contain toggle button in example query btn-group dropdown', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + it('... should contain toggle button in example query btn-group dropdown', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); - getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'button.btn.dropdown-toggle', 1, 1); - }); + getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'button.btn.dropdown-toggle', 1, 1); + }); - it('... should contain exmapleydropdown menu div with dropdown items in example query btn-group', () => { - // Header debug elements - const panelHeaderDes = getAndExpectDebugElementByCss( - compDe, - 'div#awg-graph-visualizer-sparql-query-header.accordion-header', - 1, - 1 - ); + it('... should contain exmapleydropdown menu div with dropdown items in example query btn-group', () => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - panelHeaderDes[0], - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + panelHeaderDes[0], + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); - const menuDes = getAndExpectDebugElementByCss(dropdownButtonGroupDes[0], 'div.dropdown-menu', 1, 1); + const menuDes = getAndExpectDebugElementByCss( + dropdownButtonGroupDes[0], + 'div.dropdown-menu', + 1, + 1 + ); - getAndExpectDebugElementByCss( - menuDes[0], - 'a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); - }); + getAndExpectDebugElementByCss( + menuDes[0], + 'a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); + }); - it('... should display label on dropdown items in example query btn-group', () => { - const dropdownButtonGroupDes = getAndExpectDebugElementByCss( - compDe, - 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', - 1, - 1 - ); + it('... should display label on dropdown items in example query btn-group', () => { + const dropdownButtonGroupDes = getAndExpectDebugElementByCss( + compDe, + 'div.accordion-button > div.awg-example-query-btn-group > div.btn-group.dropdown', + 1, + 1 + ); - const itemDes = getAndExpectDebugElementByCss( - dropdownButtonGroupDes[0], - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); + const itemDes = getAndExpectDebugElementByCss( + dropdownButtonGroupDes[0], + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; - expect(itemEl0.textContent).toBeTruthy(); - expect(itemEl0.textContent) - .withContext(`should contain ${expectedQueryList[0].queryLabel}`) - .toContain(expectedQueryList[0].queryLabel); + expect(itemEl0.textContent).toBeTruthy(); + expect(itemEl0.textContent) + .withContext(`should contain ${expectedQueryList[0].queryLabel}`) + .toContain(expectedQueryList[0].queryLabel); - expect(itemEl1.textContent).toBeTruthy(); - expect(itemEl1.textContent) - .withContext(`should contain ${expectedQueryList[1].queryLabel}`) - .toContain(expectedQueryList[1].queryLabel); - }); + expect(itemEl1.textContent).toBeTruthy(); + expect(itemEl1.textContent) + .withContext(`should contain ${expectedQueryList[1].queryLabel}`) + .toContain(expectedQueryList[1].queryLabel); + }); - it('... should disable current query in dropdown items in example query btn-group', async () => { - const itemDes = getAndExpectDebugElementByCss( - compDe, - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); + it('... should disable current query in dropdown items in example query btn-group', async () => { + const itemDes = getAndExpectDebugElementByCss( + compDe, + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; - expect(itemEl0).toHaveClass('disabled'); - expect(itemEl1).not.toHaveClass('disabled'); + expect(itemEl0).toHaveClass('disabled'); + expect(itemEl1).not.toHaveClass('disabled'); - component.query = expectedQuery2; - await detectChangesOnPush(fixture); + component.query = expectedConstructQuery2; + await detectChangesOnPush(fixture); - expect(itemEl0).not.toHaveClass('disabled'); - expect(itemEl1).toHaveClass('disabled'); - }); + expect(itemEl0).not.toHaveClass('disabled'); + expect(itemEl1).toHaveClass('disabled'); + }); - it('... should trigger `onQueryListChange()` by click on dropdown item', async () => { - const itemDes = getAndExpectDebugElementByCss( - compDe, - 'div.dropdown-menu > a.dropdown-item', - expectedQueryList.length, - expectedQueryList.length - ); + it('... should trigger `onQueryListChange()` by click on dropdown item', async () => { + const itemDes = getAndExpectDebugElementByCss( + compDe, + 'div.dropdown-menu > a.dropdown-item', + expectedQueryList.length, + expectedQueryList.length + ); - expect(itemDes.length) - .withContext(`should be ${expectedQueryList.length}`) - .toBe(expectedQueryList.length); + expect(itemDes.length) + .withContext(`should be ${expectedQueryList.length}`) + .toBe(expectedQueryList.length); - const itemEl0 = itemDes[0].nativeElement; - const itemEl1 = itemDes[1].nativeElement; + const itemEl0 = itemDes[0].nativeElement; + const itemEl1 = itemDes[1].nativeElement; - expect(itemEl1).not.toHaveClass('disabled'); + expect(itemEl1).not.toHaveClass('disabled'); - // Click on second item (first disabled) - click(itemEl1 as HTMLElement); - await detectChangesOnPush(fixture); + // Click on second item (first disabled) + click(itemEl1 as HTMLElement); + await detectChangesOnPush(fixture); - // Spy call with second query - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery2); + // Spy call with second query + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery2); - component.query = expectedQuery2; - await detectChangesOnPush(fixture); + component.query = expectedConstructQuery2; + await detectChangesOnPush(fixture); - expect(itemEl0).not.toHaveClass('disabled'); + expect(itemEl0).not.toHaveClass('disabled'); - // Click on first item (second disabled) - click(itemEl0 as HTMLElement); - await detectChangesOnPush(fixture); + // Click on first item (second disabled) + click(itemEl0 as HTMLElement); + await detectChangesOnPush(fixture); - // Spy call with first query - expectSpyCall(onQueryListChangeSpy, 2, expectedQuery1); + // Spy call with first query + expectSpyCall(onQueryListChangeSpy, 2, expectedConstructQuery1); + }); }); it('... should contain CodeMirrorComponent (stubbed) in panel body', () => { @@ -1058,7 +1218,11 @@ describe('SparqlEditorComponent (DONE)', () => { }); }); - describe('#isExampleQueriesEnabled', () => { + describe('#isExampleQueriesEnabled()', () => { + it('... should have `isExampleQueriesEnabled()` method', () => { + expect(component.isExampleQueriesEnabled).toBeDefined(); + }); + it('... should return true if queryList = true and query is a valid query (has queryType, queryLabel, queryString)', () => { expect(component.isExampleQueriesEnabled()).toBeTrue(); }); @@ -1119,7 +1283,7 @@ describe('SparqlEditorComponent (DONE)', () => { }); }); - describe('#onEditorInputChange', () => { + describe('#onEditorInputChange()', () => { beforeEach(async () => { // Open panel by click on header button const btnDes = getAndExpectDebugElementByCss( @@ -1135,11 +1299,15 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); }); + it('... should have `onEditorInputChange()` method', () => { + expect(component.onEditorInputChange).toBeDefined(); + }); + it('... should trigger on event from CodeMirrorComponent', () => { const codeMirrorDes = getAndExpectDebugElementByDirective(compDe, CodeMirrorStubComponent, 1, 1); const codeMirrorCmp = codeMirrorDes[0].injector.get(CodeMirrorStubComponent) as CodeMirrorStubComponent; - const changedQueryString = expectedQuery2.queryString; + const changedQueryString = expectedConstructQuery2.queryString; codeMirrorCmp.ngModelChange.emit(changedQueryString); expectSpyCall(onEditorInputChangeSpy, 1, changedQueryString); @@ -1191,7 +1359,7 @@ describe('SparqlEditorComponent (DONE)', () => { CodeMirrorStubComponent ) as CodeMirrorStubComponent; - const changedQueryString = expectedQuery2.queryString; + const changedQueryString = expectedConstructQuery2.queryString; codeMirrorCmp.ngModelChange.emit(changedQueryString); expectSpyCall(onEditorInputChangeSpy, 1, changedQueryString); @@ -1213,7 +1381,11 @@ describe('SparqlEditorComponent (DONE)', () => { }); }); - describe('#onQueryListChange', () => { + describe('#onQueryListChange()', () => { + it('... should have `onQueryListChange()` method', () => { + expect(component.onQueryListChange).toBeDefined(); + }); + it('... should trigger from click on dropdown item', async () => { const itemDes = getAndExpectDebugElementByCss( compDe, @@ -1229,9 +1401,9 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); // Spy call with second query - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery2); + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery2); - component.query = expectedQuery2; + component.query = expectedConstructQuery2; await detectChangesOnPush(fixture); expect(itemEl0).not.toHaveClass('disabled'); @@ -1241,7 +1413,7 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); // Spy call with first query - expectSpyCall(onQueryListChangeSpy, 2, expectedQuery1); + expectSpyCall(onQueryListChangeSpy, 2, expectedConstructQuery1); }); it('... should trigger resetQuery on queryList change', async () => { @@ -1258,8 +1430,8 @@ describe('SparqlEditorComponent (DONE)', () => { click(itemEl1 as HTMLElement); await detectChangesOnPush(fixture); - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery2); - expectSpyCall(resetQuerySpy, 1, expectedQuery2); + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery2); + expectSpyCall(resetQuerySpy, 1, expectedConstructQuery2); }); it('... should not trigger resetQuery if no query is provided', async () => { @@ -1272,27 +1444,27 @@ describe('SparqlEditorComponent (DONE)', () => { it('... should not trigger resetQuery if no queryList is provided', async () => { component.queryList = undefined; - component.onQueryListChange(expectedQuery2); + component.onQueryListChange(expectedConstructQuery2); await detectChangesOnPush(fixture); - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery2); + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery2); expectSpyCall(resetQuerySpy, 0, 0); }); it('... should find query in queryList and trigger resetQuery with correct query', async () => { // First query - component.onQueryListChange(expectedQuery1); + component.onQueryListChange(expectedConstructQuery1); await detectChangesOnPush(fixture); - expectSpyCall(onQueryListChangeSpy, 1, expectedQuery1); - expectSpyCall(resetQuerySpy, 1, expectedQuery1); + expectSpyCall(onQueryListChangeSpy, 1, expectedConstructQuery1); + expectSpyCall(resetQuerySpy, 1, expectedConstructQuery1); // Second query - component.onQueryListChange(expectedQuery2); + component.onQueryListChange(expectedConstructQuery2); await detectChangesOnPush(fixture); - expectSpyCall(onQueryListChangeSpy, 2, expectedQuery2); - expectSpyCall(resetQuerySpy, 2, expectedQuery2); + expectSpyCall(onQueryListChangeSpy, 2, expectedConstructQuery2); + expectSpyCall(resetQuerySpy, 2, expectedConstructQuery2); // Unknown query const otherQuery = new GraphSparqlQuery(); @@ -1303,11 +1475,51 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); expectSpyCall(onQueryListChangeSpy, 3, otherQuery); - expectSpyCall(resetQuerySpy, 3, expectedQuery1); + expectSpyCall(resetQuerySpy, 3, expectedConstructQuery1); + }); + }); + + // TODO onViewChange + describe('#onViewChange()', () => { + it('... should have `onViewChange()` method', () => { + expect(component.onViewChange).toBeDefined(); + }); + + it('... should trigger on event from view handle button group', fakeAsync(() => { + // Header debug elements + const panelHeaderDes = getAndExpectDebugElementByCss( + compDe, + 'div#awg-graph-visualizer-sparql-query-header.accordion-header', + 1, + 1 + ); + + // ViewHandleButtongGroupComponent debug elements + const viewHandleButtongGroupDes = getAndExpectDebugElementByDirective( + panelHeaderDes[0], + ViewHandleButtongGroupStubComponent, + 1, + 1 + ); + const viewHandleButtongGroupCmp = viewHandleButtongGroupDes[0].injector.get( + ViewHandleButtongGroupStubComponent + ) as ViewHandleButtongGroupStubComponent; + + viewHandleButtongGroupCmp.viewChangeRequest.emit(ViewHandleTypes.GRAPH); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.GRAPH); + })); + + it('... should trigger switchQueryType(), onEditorInputChange() with queryString and performQuery()', () => { + component.onViewChange(ViewHandleTypes.GRAPH); + + expectSpyCall(switchQueryTypeSpy, 1, ViewHandleTypes.GRAPH); + expectSpyCall(onEditorInputChangeSpy, 1, expectedConstructQuery1.queryString); + expectSpyCall(performQuerySpy, 1); }); }); - describe('#performQuery', () => { + describe('#performQuery()', () => { beforeEach(async () => { // Open panel by click on header button const btnDes = getAndExpectDebugElementByCss( @@ -1323,6 +1535,10 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); }); + it('... should have `performQuery()` method', () => { + expect(component.performQuery).toBeDefined(); + }); + it('... should trigger from click on Query button', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, @@ -1392,7 +1608,7 @@ describe('SparqlEditorComponent (DONE)', () => { }); }); - describe('#resetQuery', () => { + describe('#resetQuery()', () => { beforeEach(async () => { // Open panel by click on header button const btnDes = getAndExpectDebugElementByCss( @@ -1408,6 +1624,10 @@ describe('SparqlEditorComponent (DONE)', () => { await detectChangesOnPush(fixture); }); + it('... should have `resetQuery()` method', () => { + expect(component.resetQuery).toBeDefined(); + }); + it('... should trigger from click on Reset button', async () => { const btnDes = getAndExpectDebugElementByCss( compDe, @@ -1457,7 +1677,134 @@ describe('SparqlEditorComponent (DONE)', () => { }); }); - describe('#togglePanel', () => { + describe('#setViewType()', () => { + it('... should have `setViewType()` method', () => { + expect(component.setViewType).toBeDefined(); + }); + + it('... should trigger on init', () => { + expectSpyCall(setViewTypeSpy, 1); + }); + + it('... should trigger on changes of query', () => { + expectSpyCall(setViewTypeSpy, 1); + + // Directly trigger ngOnChanges + component.ngOnChanges({ + query: new SimpleChange(component.query, [expectedSelectQuery1], false), + }); + + expectSpyCall(setViewTypeSpy, 2); + }); + + it('... should only trigger on changes of query if not first change', () => { + expectSpyCall(setViewTypeSpy, 1); + + // Directly trigger ngOnChanges + component.ngOnChanges({ + query: new SimpleChange(component.query, [expectedSelectQuery1], true), + }); + + expectSpyCall(setViewTypeSpy, 1); + }); + + it('... should return ViewHandleTypes.GRAPH if querytype is `construct`', () => { + component.query = expectedConstructQuery1; + component.setViewType(); + + expect(component.selectedViewType).toBe(ViewHandleTypes.GRAPH); + + component.query = expectedConstructQuery2; + component.setViewType(); + + expect(component.selectedViewType).toBe(ViewHandleTypes.GRAPH); + }); + + it('... should return ViewHandleTypes.TABLE if querytype is `select`', () => { + component.query = expectedSelectQuery1; + component.setViewType(); + + expect(component.selectedViewType).toBe(ViewHandleTypes.TABLE); + + component.query = expectedSelectQuery2; + component.setViewType(); + + expect(component.selectedViewType).toBe(ViewHandleTypes.TABLE); + }); + }); + + describe('#switchQueryType()', () => { + it('... should have `switchQueryType()` method', () => { + expect(component.switchQueryType).toBeDefined(); + }); + + it('... should switch querytype and string to `select` if requested view is `table`', () => { + component.query.queryType = expectedConstructQuery1.queryType; + component.query.queryString = expectedConstructQuery1.queryString; + + component.switchQueryType(ViewHandleTypes.TABLE); + + expect(component.query.queryType).toBeTruthy(); + expect(component.query.queryType) + .withContext(`should be ${expectedSelectQuery1.queryType}`) + .toBe(expectedSelectQuery1.queryType); + expect(component.query.queryString).toBeTruthy(); + expect(component.query.queryString) + .withContext(`should be ${expectedSelectQuery1.queryString}`) + .toBe(expectedSelectQuery1.queryString); + }); + + it('... should switch querytype and string to `construct` if requested view is `graph`', () => { + // Switch to TABLE view + component.switchQueryType(ViewHandleTypes.TABLE); + + component.query.queryType = expectedSelectQuery1.queryType; + expect((component.query.queryString = expectedSelectQuery1.queryString)); + + // Switch back to GRAPH view + component.switchQueryType(ViewHandleTypes.GRAPH); + + expect(component.query.queryType).toBeTruthy(); + expect(component.query.queryType) + .withContext(`should be ${expectedConstructQuery1.queryType}`) + .toBe(expectedConstructQuery1.queryType); + expect(component.query.queryString).toBeTruthy(); + expect(component.query.queryString) + .withContext(`should be ${expectedConstructQuery1.queryString}`) + .toBe(expectedConstructQuery1.queryString); + }); + + it('... should do nothing if requested view is `grid`', () => { + component.query.queryType = expectedConstructQuery1.queryType; + component.query.queryString = expectedConstructQuery1.queryString; + + component.switchQueryType(ViewHandleTypes.GRID); + + expect(component.query.queryType).toBeTruthy(); + expect(component.query.queryType) + .withContext(`should be ${expectedConstructQuery1.queryType}`) + .toBe(expectedConstructQuery1.queryType); + expect(component.query.queryString).toBeTruthy(); + expect(component.query.queryString) + .withContext(`should be ${expectedConstructQuery1.queryString}`) + .toBe(expectedConstructQuery1.queryString); + }); + + it('... should throw error if requested view is not `table`, `graph` or `grid`', () => { + component.query.queryType = expectedConstructQuery1.queryType; + component.query.queryString = expectedConstructQuery1.queryString; + + expect(() => component.switchQueryType(undefined)).toThrowError( + `The view must be ${ViewHandleTypes.GRAPH} or ${ViewHandleTypes.TABLE}, but was: undefined.` + ); + }); + }); + + describe('#togglePanel()', () => { + it('... should have `togglePanel()` method', () => { + expect(component.togglePanel).toBeDefined(); + }); + it('... should return empty string if isFullscreen = false', () => { expect(component.togglePanel()).not.toBeTruthy(); }); @@ -1472,6 +1819,10 @@ describe('SparqlEditorComponent (DONE)', () => { }); describe('#preventPanelCollapseOnFullscreen', () => { + it('... should have `preventPanelCollapseOnFullscreen()` method', () => { + expect(component.preventPanelCollapseOnFullscreen).toBeDefined(); + }); + it('... should trigger on event from ngb-accordion', () => { const accordionDes = getAndExpectDebugElementByDirective(compDe, NgbAccordion, 1, 1); const accordionCmp = accordionDes[0].injector.get(NgbAccordion) as NgbAccordion; diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts index c7c98ab203..d0bbe26bd2 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/sparql-editor/sparql-editor.component.ts @@ -1,9 +1,20 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + ViewChild, +} from '@angular/core'; import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; import { faDiagramProject, faTable } from '@fortawesome/free-solid-svg-icons'; import { ToastMessage } from '@awg-shared/toast/toast.service'; +import { ViewHandle, ViewHandleTypes } from '@awg-shared/view-handle-button-group/view-handle.model'; import { GraphSparqlQuery } from '@awg-views/edition-view/models'; import { CmConfig } from '../models'; @@ -21,7 +32,7 @@ import 'codemirror/mode/sparql/sparql'; styleUrls: ['./sparql-editor.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SparqlEditorComponent { +export class SparqlEditorComponent implements OnInit, OnChanges { /** * ViewChild variable: sparqlAcc. * @@ -98,6 +109,127 @@ export class SparqlEditorComponent { mode: 'sparql', }; + /** + * Public variable: faDiagramProject. + * + * It instantiates fontawesome's faDiagramProject icon. + */ + faDiagramProject = faDiagramProject; + + /** + * Public variable: faTable. + * + * It instantiates fontawesome's faTable icon. + */ + faTable = faTable; + + /** + * Public variable: selectedViewType. + * + * It keeps the selected view type. + */ + selectedViewType: ViewHandleTypes = ViewHandleTypes.GRAPH; + + /** + * Public variable: viewHandles. + * + * It keeps the list of view handles. + */ + viewHandles: ViewHandle[] = [ + new ViewHandle('Graph view', ViewHandleTypes.GRAPH, faDiagramProject), + new ViewHandle('Table view', ViewHandleTypes.TABLE, faTable), + ]; + + /** + * Angular life cycle hook: ngOnInit. + * + * It calls the containing methods + * when initializing the component. + */ + ngOnInit() { + this.setViewType(); + } + + /** + * Angular life cycle hook: ngOnChanges. + * + * It checks for changes of the given input. + * + * @param {SimpleChanges} changes The changes of the input. + */ + ngOnChanges(changes: SimpleChanges) { + if (changes['query'] && !changes['query'].isFirstChange()) { + this.setViewType(); + } + } + + /** + * Public method: setViewType. + * + * It sets the view type according to the query type. + * + * @returns {void} Sets the selected view type. + */ + setViewType(): void { + if (this.query.queryType && this.query.queryType === 'construct') { + this.selectedViewType = ViewHandleTypes.GRAPH; + } + if (this.query.queryType && this.query.queryType === 'select') { + this.selectedViewType = ViewHandleTypes.TABLE; + } + } + + /** + * Public method: onViewChange. + * + * It switches the query type according to the given view type and + * triggers onEditorInputChange and performQuery. + * + * @param {ViewHandleTypes} view The given view type. + * + * @returns {void} Performs a new query with switched query type. + */ + onViewChange(view: ViewHandleTypes): void { + this.switchQueryType(view); + this.onEditorInputChange(this.query.queryString); + this.performQuery(); + } + + /** + * Public method: switchQueryType. + * + * It switches the query type and string according to the given view type. + * + * @param {ViewHandleTypes} view The given view type. + * + * @returns {void} Switches the query type. + */ + switchQueryType(view: ViewHandleTypes): void { + switch (view) { + case ViewHandleTypes.TABLE: + if (this.query.queryType === 'construct' && this.query.queryString.includes('CONSTRUCT')) { + this.query.queryString = this.query.queryString.replace('CONSTRUCT', 'SELECT *'); + this.query.queryType = 'select'; + } + break; + case ViewHandleTypes.GRAPH: + if (this.query.queryType === 'select' && this.query.queryString.includes('SELECT')) { + this.query.queryString = this.query.queryString.replace(/SELECT.*\n/, 'CONSTRUCT\n'); + this.query.queryType = 'construct'; + } + break; + case ViewHandleTypes.GRID: + // Do nothing + break; + default: + // This branch should not be reached + const exhaustiveCheck: never = view; + throw new Error( + `The view must be ${ViewHandleTypes.GRAPH} or ${ViewHandleTypes.TABLE}, but was: ${exhaustiveCheck}.` + ); + } + } + /** * Public method: isExampleQueriesEnabled. * @@ -169,8 +301,7 @@ export class SparqlEditorComponent { /** * Public method: resetQuery. * - * It emits a trigger to - * the {@link resetQueryRequest}. + * It emits a trigger to the {@link resetQueryRequest}. * * @param {GraphSparqlQuery} query The given triples. * From df94020abcc29c23ded489d3d519095261cae063 Mon Sep 17 00:00:00 2001 From: chael-mi Date: Thu, 7 Jul 2022 16:31:23 +0200 Subject: [PATCH 023/122] feat(assets): add files for M 34 --- .../2a/M34/M34_source-description.json | 28 +++++ .../2/section/2a/M34/M34_source-list.json | 18 +++ .../2/section/2a/M34/M34_textcritics.json | 112 ++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/assets/data/edition/series/2/section/2a/M34/M34_source-description.json create mode 100644 src/assets/data/edition/series/2/section/2a/M34/M34_source-list.json create mode 100644 src/assets/data/edition/series/2/section/2a/M34/M34_textcritics.json diff --git a/src/assets/data/edition/series/2/section/2a/M34/M34_source-description.json b/src/assets/data/edition/series/2/section/2a/M34/M34_source-description.json new file mode 100644 index 0000000000..0aa1c1fccb --- /dev/null +++ b/src/assets/data/edition/series/2/section/2a/M34/M34_source-description.json @@ -0,0 +1,28 @@ +{ + "sources": [ + { + "id": "sourceA", + "siglum": "A", + "type": "Skizzen", + "location": "CH-Bps, Sammlung Anton Webern.", + "description": [ + "1 Bogen (Bl. 1/2): Der Bogen ist Bestandteil (Bl. 1/2) eines größeren Konvoluts aus 2 Bögen (Bl. 1/2, 3/4), 2 Blättern (Bl. 5–6) und 1 Bogen (Bl. 7/8).", + "Beschreibstoff: Notenpapier, 18 Systeme, Format (hoch): 349 × 265 mm, Firmenzeichen [JE] | Protokoll. Schutzmarke | No. 5 | 18 linig. unten links auf Bl. 1r–2r.", + "Schreibstoff: Bleistift.", + "Inhalt:
M 34 Sk1 (Skizze zu Studienkomposition für Klavier M 34):
Bl. 1r System 1–2: T. 1–7;
System 4–5: T. 8–14;
System 7a–8a: T. 15–16.
M 34 Sk1.1 (Skizze zu Studienkomposition für Klavier M 34):
Bl. 1r System 7b–8b: T. 5–8.
M 35 Sk1 (Skizze zu Studienkomposition für Klavier M 35):
Bl. 1r System 10–11: T. 1–7;
System 13a–14a: T. 8 1–2/4, {8 3/4A} bis {9A}, {10}, 8 3/4B bis 9B.
M 36 Sk1 (Skizze zu Studienkomposition für Klavier M 36):
Bl. 1r System 16–17: T. 1–6;
System 14c, 15b: T. 7;
System 13–14: T. 8–11;
System 12: T. 12;
System 15a: T. 16.
M* 404 Sk1 (Skizze zu Studienkomposition für Klavier M* 404):
Bl. 1v System 1–2: T. 1–4, {5A};
System 4–5: T. 5B, {6A}–{7A}, 6B–7B;
System 10–11: T. 8A–9A, 8B 3/4 bis 9B, 10–11;
System 13a–14a: T. 12–15;
System 16a–17a: T. 16.
M* 404 Sk1.1 (Skizze zu Studienkomposition für Klavier M* 404):
Bl. 1v System 7a–8a: T. 1–2.
M* 404 Sk1.2 (Skizze zu Studienkomposition für Klavier M* 404):
Bl. 1v System 7b–8b: T. 1–2.
M* 404 Sk1.3 (Skizze zu Studienkomposition für Klavier M* 404):
Bl. 1v System 7c–8c: T. x+1.
M* 404 Sk1.4 (Skizze zu Studienkomposition für Klavier M* 404):
Bl. 1v System 16b–17b: T. 16–17.
M 38 Sk3 (Skizze zu Studienkomposition für Klavier M 38):
Bl. 1v System 15, 16c: T. 1–4;
System 13b–14b: T. 5;
System 17c, 18: T. 6–8.
Bl. 2v System 1–2: T. 9–10, {11A}–12A}, 11B–12B;
System 4–5: T. 13–17.
M 39 Sk1 (Skizze zu Studienkomposition für Klavier M 39):
Bl. 2r System 2a–3a: T. 1, {2A};
System 5–6: T. {2B}, 3–4, 5A;
System 8a–9a: T. 5B;
System 11a–12a: T. 6A, {7A}–{8A};
System 13a–14a: T. 6B–8B;
System 16–17: T. {9B}, {10A}, {11 1–2/4}, {11 2–4/4}, {12A}–{13A};
System 9c, 10: T. {9B};
System 11b–12b: T. 9C, 10B;
System 13b–14b: T. 11C, 12B;
System 7–8c: T. 13B, 14.
M 39 Sk1.1 (Skizze zu Studienkomposition für Klavier M 39):
Bl. 2r System 8b–9b: T. 2.
M 37 Sk2 (Skizze zu Studienkomposition für Klavier M 37):
Bl. 2r System 18a: T. 9–12.
M 37 Sk3 (Skizze zu Studienkomposition für Klavier M 37):
Bl. 2r System 18b: T. 9–13.
M 38 Sk2 (Skizze zu Studienkomposition für Klavier M 38):
Bl. 2r System 18c: T. 9–11.
M 38 Sk1 (Skizze zu Studienkomposition für Klavier M 38):
Bl. 2r System nach 18: T. 1–12.
M 37 Sk1 (Skizze zu Studienkomposition für Klavier M 37):
Bl. 2v System 7–8: T. 1–6;
System 10–11: T. 7–14;
System 13–14: T. 15–21." + ] + }, + { + "id": "sourceB", + "siglum": "B", + "type": "Tintenniederschrift", + "location": "CH-Bps, Sammlung Anton Webern.", + "description": [ + "1 Bogen (Bl. 1/2): Der Bogen ist Bestandteil (Bl. 3/4) eines größeren Konvoluts aus 2 Bögen (Bl. 1/2, 3/4), 2 Blättern (Bl. 5–6) und 1 Bogen (Bl. 7/8). Stockfleck und rötliche Verfärbung mittig am oberen Rand des Bogens.", + "Beschreibstoff: Notenpapier, 18 Systeme, Format (hoch): 349 × 267 mm, Firmenzeichen [JE] | Protokoll. Schutzmarke | No. 5 | 18 linig. unten links auf Bl. 1r–2r.", + "Schreibstoff: schwarze Tint; Bleistift.", + "Inhalt:
M 34 (Tintenniederschrift von Studienkomposition für Klavier M 34):
Bl. 1r System 1–2: T. 1–5;
System 4–5: T. 6–8.
M 35 (Tintenniederschrift von Studienkomposition für Klavier M 35):
Bl. 1r System 7–8: T. 1–7;
System 10a–11a: T. 8–9.
M 35 Sk2 (Skizze zu Studienkomposition für Klavier M 35):
Bl. 1r System 10b–11b (Bleistift): T. 7 2–3/4 bis 9.
M 36 (Tintenniederschrift von Studienkomposition für Klavier M 36):
Bl. 1r System 13–14: T. 1–6;
System 16–17: T. 7–13;
System 1–2: T. 14–16.
M 37 (Tintenniederschrift von Studienkomposition für Klavier M 37):
Bl. 1v System 4–5: T. 1–7;
System 7–8: T. 8–15;
System 10–11: 16–21.
M 38 (Tintenniederschrift von Studienkomposition für Klavier M 38):
Bl. 1v System 13–14: T. 1–6;
System 15–16: 7–12;
System 17–18: 13–20.
M 39 (Tintenniederschrift von Studienkomposition für Klavier M 39: Textfassung 1→2):
Bl. 2r System 1–2: T. 1–3;
System 4–5: T. 4–7;
System 7–8: T. 8–12 → T. 8, {9–12};
System 10–11: T. 13–16 → T. {13–14}, 13–14;
System 13–14: T. 17–19 → T. 15–17.
M 39 Sk2(Skizze zu Studienkomposition für Klavier M 39: Textfassung 2):
Bl. 2r System 16a–17a (Bleistift): T. 9–10.
M 39 Sk3(Tintenniederschrift von Studienkomposition für Klavier M 39: Textfassung 2):
Bl. 2r System 13b–14b (Bleistift): T. 16 bis 17 1–2/4;
System 16b–17b (Bleistift): T. 17 3–4/4 bis 19.
M 41(Tintenniederschrift von Studienkomposition für Klavier M 40):
Bl. 2v System 1–2: T. 1–6;
System 4–5: T. 7–12;
System 7–8: T. 13–18;
System 10–11: T. 19–25;
System 13–14: T. 26–30;
System 16–17: T. 31." + ] + } + ] +} \ No newline at end of file diff --git a/src/assets/data/edition/series/2/section/2a/M34/M34_source-list.json b/src/assets/data/edition/series/2/section/2a/M34/M34_source-list.json new file mode 100644 index 0000000000..479b88057a --- /dev/null +++ b/src/assets/data/edition/series/2/section/2a/M34/M34_source-list.json @@ -0,0 +1,18 @@ +{ + "sources": [ + { + "siglum": "A", + "type": "Skizzen zu
Studienkomposition für Klavier.
Enthält auch Skizzen zu Studienkompositionen für Klavier M 35–38, M 39 Textfassung 1→2 und M* 404.", + "location": "CH-Bps, Sammlung Anton Webern.", + "hasDescription": true, + "linkTo": "sourceA" + }, + { + "siglum": "B", + "type": "Tintenniederschrift von
Studienkomposition für Klavier M 34.
Enthält auch Tintenniederschrift zu Studienkompositionen für Klavier M 35–38 und M 39 Textfassung 1→2 sowie Skizzen zu Studienkomposition für Klavier M 35 und M 39 Textfassung 2.", + "location": "CH-Bps, Sammlung Anton Webern.", + "hasDescription": true, + "linkTo": "sourceB" + } + ] +} diff --git a/src/assets/data/edition/series/2/section/2a/M34/M34_textcritics.json b/src/assets/data/edition/series/2/section/2a/M34/M34_textcritics.json new file mode 100644 index 0000000000..a297764d0b --- /dev/null +++ b/src/assets/data/edition/series/2/section/2a/M34/M34_textcritics.json @@ -0,0 +1,112 @@ +{ + "textcritics": [ + { + "id": "M_34_Sk1", + "label": "M 34 Sk1", + "description": [ + "M 34 Sk1 ist die Verlaufsskizze zu M 34, die jedoch ab T. 7–15 maßgeblich von der späteren achttaktigen Tintenniederschrift abweicht und stattdessen eine sechzehntaktige Periode formuliert." + ], + "comments": [ + { + "measure": "1", + "system": "2", + "position": "2/4", + "comment": "B1 überschreibt f." + }, + { + "measure": "1", + "system": "2", + "position": "3/4", + "comment": "d/f überschreibt b/d1/f1." + }, + { + "measure": "4", + "system": "1", + "position": "", + "comment": "Oberstimmenschicht: Halbe Note c3 und Viertelnote a1/f2 überschreiben punktierte Viertelnote a2/c3 und Achtelnoten f2/a2–f2–e2." + }, + { + "measure": "4", + "system": "1", + "position": "1–2/4", + "comment": "Unterstimmenschicht: Achtelnoten a1/c2–g1/b2 und Viertelnote f1/a1 gestrichen." + }, + { + "measure": "4", + "system": "2", + "position": "3/4", + "comment": "Viertelnote f/b gestrichen und ersetzt durch f/a." + }, + { + "measure": "11", + "system": "4", + "position": "3/4", + "comment": "b1 überschreibt c2." + }, + { + "measure": "13", + "system": "5", + "position": "1. Pause", + "comment": "Viertelpause überschreibt Achtelpause." + }, + { + "measure": "14", + "system": "4", + "position": "3/8", + "comment": "b überscheibt es1." + }, + { + "measure": "15", + "system": "7", + "position": "4/8", + "comment": "f1 radiert." + }, + { + "measure": "15", + "system": "8", + "position": "3/4", + "comment": "Notenkopf F radiert." + }, + { + "measure": "15", + "system": "7", + "position": "6/8", + "comment": "f2 gestrichen." + }, + { + "measure": "16", + "system": "7–8", + "position": "1. Note", + "comment": "Halbe Note überschreibt Viertelnote." + } + ] + }, + { + "id": "M_34_Sk1.1", + "label": "M 34 Sk1.1", + "description": [ + "M 34 Sk1.1entwirft auf der Basis von M 34 Sk1eine alternative Fortsetzung ab T. 5, die zu einer Verkürzung auf eine achttaktige Periode analog der späteren Tintenniederschrift führt." + ], + "comments": [ + { + "measure": "5", + "system": "7", + "position": "3/4", + "comment": "f2 überschreibt g2." + }, + { + "measure": "5", + "system": "8", + "position": "3/4", + "comment": "D überschreibt G." + }, + { + "measure": "6", + "system": "8", + "position": "3–4/8", + "comment": "Oberstimmenschicht: Zwei Achtelnoten überschreiben punktierte Viertelnote und Achtelnote? Entzifferung unklar." + } + ] + } + ] +} From 411051faf30d9e8097af6fc1fa0ea8b441a45f1a Mon Sep 17 00:00:00 2001 From: chael-mi Date: Thu, 7 Jul 2022 20:23:31 +0200 Subject: [PATCH 024/122] feat(assets): add svg for M 34 --- .../2/section/2a/M 34/Skizzen M34_Sk1(1).svg | 2736 +++++++++++++++++ .../2/section/2a/M 34/Skizzen M34_Sk1(2).svg | 2224 ++++++++++++++ .../2/section/2a/M 34/Skizzen M34_Sk1(3).svg | 1078 +++++++ .../2/section/2a/M 34/Skizzen M34_Sk1.1.svg | 1717 +++++++++++ 4 files changed, 7755 insertions(+) create mode 100644 src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(1).svg create mode 100644 src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(2).svg create mode 100644 src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(3).svg create mode 100644 src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1.1.svg diff --git a/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(1).svg b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(1).svg new file mode 100644 index 0000000000..f51e9a0665 --- /dev/null +++ b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(1).svg @@ -0,0 +1,2736 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +& +? +b +b +b +b +œ +1 +2 +œ +œ +œ +Œ +œ +œ +œ +œ +1 +. +. +œ +œ +j +œ +œ +œ +œ +. +œ +j +œ +œ +œ +œ +2 +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +3 +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +. +˙ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +4 +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +5 +. +. +œ +œ +j +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +6 +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +b +7 + + + + + + + + +A: Bl. 1r +M 34: Sk1 T. 1–7 +. +œ + diff --git a/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(2).svg b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(2).svg new file mode 100644 index 0000000000..072cc16026 --- /dev/null +++ b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(2).svg @@ -0,0 +1,2224 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +& +? +b +b +b +b +. +œ +# +œ +œ +œ +œ +œ +œ +œ +œ +b +œ +œ +œ +œ +œ +œ +n +œ +œ +œ +˙ +4 +5 +8 +˙ +œ +˙ +œ +˙ +˙ +œ +œ +9 +œ +œ +œ +n +œ +œ +œ +œ +œ +œ +. +˙ +b +10 +˙ +˙ +œ +œ +œ +œ +œ +œ +. +˙ +n +11 +œ +œ +œ +œ +œ +œ +b +œ +œ +b +˙ +œ +œ +œ +12 +œ +œ +œ +œ +œ +œ +Œ +œ +œ + +13 +. +œ +j +œ +œ +j +œ +. +œ +j +œ +œ +œ +œ +14 + + + + + + + +A: Bl. 1r +M 34: Sk1 T. 8–14 +n +# +œ + diff --git a/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(3).svg b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(3).svg new file mode 100644 index 0000000000..caa5b55b6c --- /dev/null +++ b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1(3).svg @@ -0,0 +1,1078 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +& +? +b +b +b +b +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +œ +7 +8 +15 +œ +œ +˙ +˙ +Œ +˙ +Œ +œ +œ +˙ +16 + + +Sk1.1 + + + + + +A: Bl. 1r +M 34: Sk1 T. 15–16 + diff --git a/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1.1.svg b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1.1.svg new file mode 100644 index 0000000000..c75f7d1f5e --- /dev/null +++ b/src/assets/img/edition/series/2/section/2a/M 34/Skizzen M34_Sk1.1.svg @@ -0,0 +1,1717 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +b + +b + +& + +b + +b + +& + + + + + +7 + +8 + +6 + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +5 + +. + +. + +œ + +œ + +j + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +. + +œ + +j + +6 + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +œ + +b + +œ + +œ + +œ + +œ + +n + +œ + +œ + +œ + +œ + +œ + +œ + +b + +œ + +œ + +b + +œ + +œ + +7 + +œ + +œ + +œ + +œ + +œ + +œ + +8 + +Sk1 T. 15–16 + + + + + + + + + + + + + + + + +A: Bl. 1r + +M 34: Sk1.1 + +b + +? + +b + + From 1402141e926ce43c07907fd3845fb83c0a2fce2d Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 10 Jul 2022 13:56:31 +0200 Subject: [PATCH 025/122] test(edition): fix tests for GraphVisualizer after changes --- .../graph-visualizer.component.spec.ts | 164 ++++++++++-------- .../graph-visualizer.component.ts | 1 + 2 files changed, 92 insertions(+), 73 deletions(-) diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts index 2b0da6ec76..9f8daa5854 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.spec.ts @@ -101,7 +101,8 @@ describe('GraphVisualizerComponent (DONE)', () => { let expectedResult: Triple[]; let expectedIsFullscreen: boolean; - let consoleSpy; + let consoleSpy: Spy; + let mockGraphVisualizerServiceGetQueryTypeSpy: Spy; let queryLocalStoreSpy: Spy; let performQuerySpy: Spy; let resetQuerySpy: Spy; @@ -172,6 +173,7 @@ describe('GraphVisualizerComponent (DONE)', () => { // Spies on component functions // `.and.callThrough` will track the spy down the nested describes, see // https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3Eand.callThrough%3C/code%3E + mockGraphVisualizerServiceGetQueryTypeSpy = spyOn(mockGraphVisualizerService, 'getQuerytype').and.callThrough(); queryLocalStoreSpy = spyOn(component, '_queryLocalStore').and.callThrough(); performQuerySpy = spyOn(component, 'performQuery').and.callThrough(); resetQuerySpy = spyOn(component, 'resetQuery').and.callThrough(); @@ -434,87 +436,94 @@ describe('GraphVisualizerComponent (DONE)', () => { .toEqual(changedQuery.queryType); }); - it('... should not find and reset a query from queryList if only queryLabel is known but not queryType', () => { - expectSpyCall(resetQuerySpy, 1, undefined); + describe('... should set query as is, and not find from queryList, if', () => { + it('... only queryLabel is known but not queryType', () => { + expectSpyCall(resetQuerySpy, 1, undefined); - // Request for query with known queryLabel but unknown queryType - const changedQuery = { ...expectedGraphRDFData.queryList[1] }; - changedQuery.queryType = 'select'; + // Request for query with known queryLabel but unknown queryType + const changedQuery = { ...expectedGraphRDFData.queryList[1] }; + changedQuery.queryType = 'select'; - component.resetQuery(changedQuery); - fixture.detectChanges(); + // Set correct return value of service + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue(changedQuery.queryType); - // Matches queryList queries only by label - expectSpyCall(resetQuerySpy, 2, undefined); + component.resetQuery(changedQuery); + fixture.detectChanges(); - expect(component.query).toBeDefined(); - expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); + // Matches queryList queries only by label + expectSpyCall(resetQuerySpy, 2, undefined); - expect(component.query.queryLabel).toBeDefined(); - expect(component.query.queryLabel) - .withContext(`should equal ${changedQuery.queryLabel}`) - .toBe(changedQuery.queryLabel); + expect(component.query).toBeDefined(); + expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); - expect(component.query.queryType).toBeDefined(); - expect(component.query.queryType) - .withContext(`should equal ${changedQuery.queryType}`) - .toBe(changedQuery.queryType); - }); + expect(component.query.queryLabel).toBeDefined(); + expect(component.query.queryLabel) + .withContext(`should equal ${changedQuery.queryLabel}`) + .toBe(changedQuery.queryLabel); - it('... should not find and reset a query from queryList if only queryType is known but not queryLabel', () => { - expectSpyCall(resetQuerySpy, 1, undefined); + expect(component.query.queryType).toBeDefined(); + expect(component.query.queryType) + .withContext(`should equal ${changedQuery.queryType}`) + .toBe(changedQuery.queryType); + }); - // Request for query with known queryType but unknown label - const changedQuery = { ...expectedGraphRDFData.queryList[1] }; - changedQuery.queryLabel = 'select all bananas'; + it('... only queryType is known but not queryLabel', () => { + expectSpyCall(resetQuerySpy, 1, undefined); - component.resetQuery(changedQuery); - fixture.detectChanges(); + // Request for query with known queryType but unknown label + const changedQuery = { ...expectedGraphRDFData.queryList[1] }; + changedQuery.queryLabel = 'select all tests'; - // Matches queryList queries only by type - expectSpyCall(resetQuerySpy, 2, undefined); + component.resetQuery(changedQuery); + fixture.detectChanges(); - expect(component.query).toBeDefined(); - expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); + // Matches queryList queries only by type + expectSpyCall(resetQuerySpy, 2, undefined); - expect(component.query.queryLabel).toBeDefined(); - expect(component.query.queryLabel) - .withContext(`should equal ${changedQuery.queryLabel}`) - .toBe(changedQuery.queryLabel); + expect(component.query).toBeDefined(); + expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); - expect(component.query.queryType).toBeDefined(); - expect(component.query.queryType) - .withContext(`should equal ${changedQuery.queryType}`) - .toBe(changedQuery.queryType); - }); + expect(component.query.queryLabel).toBeDefined(); + expect(component.query.queryLabel) + .withContext(`should equal ${changedQuery.queryLabel}`) + .toBe(changedQuery.queryLabel); - it('... should set an unkown query that is not in queryList as is', () => { - expectSpyCall(resetQuerySpy, 1, undefined); + expect(component.query.queryType).toBeDefined(); + expect(component.query.queryType) + .withContext(`should equal ${changedQuery.queryType}`) + .toBe(changedQuery.queryType); + }); - // Request for unknown query - const changedQuery = { - queryType: 'SELECT', - queryLabel: 'Test Query 3', - queryString: - 'PREFIX example: \n\n SELECT * WHERE { ?test3 ?has ?success3 . }', - }; - component.resetQuery(changedQuery); - fixture.detectChanges(); + it('... given query is not in queryList', () => { + expectSpyCall(resetQuerySpy, 1, undefined); - expectSpyCall(resetQuerySpy, 2, undefined); + // Request for unknown query + const changedQuery = { + queryType: 'select', + queryLabel: 'Test Query 3', + queryString: + 'PREFIX example: \n\n SELECT * WHERE { ?test3 ?has ?success3 . }', + }; + // Set correct return value of service + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue(changedQuery.queryType); + component.resetQuery(changedQuery); + fixture.detectChanges(); - expect(component.query).toBeDefined(); - expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); + expectSpyCall(resetQuerySpy, 2, undefined); - expect(component.query.queryLabel).toBeDefined(); - expect(component.query.queryLabel) - .withContext(`should equal ${changedQuery.queryLabel}`) - .toBe(changedQuery.queryLabel); + expect(component.query).toBeDefined(); + expect(component.query).withContext(`should equal ${changedQuery}`).toEqual(changedQuery); - expect(component.query.queryType).toBeDefined(); - expect(component.query.queryType) - .withContext(`should equal ${changedQuery.queryType}`) - .toBe(changedQuery.queryType); + expect(component.query.queryLabel).toBeDefined(); + expect(component.query.queryLabel) + .withContext(`should equal ${changedQuery.queryLabel}`) + .toBe(changedQuery.queryLabel); + + expect(component.query.queryType).toBeDefined(); + expect(component.query.queryType) + .withContext(`should equal ${changedQuery.queryType}`) + .toBe(changedQuery.queryType); + }); }); it('... should set initial query (queryList[0]) if no query is provided', () => { @@ -540,7 +549,7 @@ describe('GraphVisualizerComponent (DONE)', () => { .toEqual(expectedGraphRDFData.queryList[0]); }); - it('... should not do anything if no queryList is provided from rdf data', () => { + it('... should not do anything if no queryList is provided from RDF data', () => { expectSpyCall(resetQuerySpy, 1, undefined); // Set undefined triples @@ -630,22 +639,30 @@ describe('GraphVisualizerComponent (DONE)', () => { it('... should get queryType from service', () => { expectSpyCall(performQuerySpy, 1, undefined); - - const queryTypeSpy = spyOn(graphVisualizerService, 'getQuerytype').and.callThrough(); + expectSpyCall( + mockGraphVisualizerServiceGetQueryTypeSpy, + 1, + expectedGraphRDFData.queryList[0].queryString + ); // Perform query component.performQuery(); fixture.detectChanges(); expectSpyCall(performQuerySpy, 2, undefined); - expectSpyCall(queryTypeSpy, 1, expectedGraphRDFData.queryList[0].queryString); + expectSpyCall( + mockGraphVisualizerServiceGetQueryTypeSpy, + 2, + expectedGraphRDFData.queryList[0].queryString + ); expect(component.query.queryType).toBeDefined(); expect(component.query.queryType).withContext('should equal construct').toEqual('construct'); }); it('... should trigger `_queryLocalStore` for construct queries', () => { - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('construct'); + // Set construct query type + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('construct'); // Perform query component.performQuery(); @@ -661,7 +678,8 @@ describe('GraphVisualizerComponent (DONE)', () => { }); it('... should trigger `_queryLocalStore` for select queries', () => { - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('select'); + // Set select query type + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('select'); // Perform query component.performQuery(); @@ -678,7 +696,7 @@ describe('GraphVisualizerComponent (DONE)', () => { it('... should get queryResult for construct queries', waitForAsync(() => { // Set construct query type - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('construct'); + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('construct'); // Perform query component.performQuery(); @@ -692,7 +710,7 @@ describe('GraphVisualizerComponent (DONE)', () => { it('... should get queryResult for select queries', waitForAsync(() => { // Set select query type - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('select'); + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('select'); // Perform query component.performQuery(); @@ -705,7 +723,7 @@ describe('GraphVisualizerComponent (DONE)', () => { })); it('... should set empty observable for update query types', waitForAsync(() => { - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('update'); + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('update'); // Perform query component.performQuery(); @@ -717,7 +735,7 @@ describe('GraphVisualizerComponent (DONE)', () => { })); it('... should set empty observable for other query types', waitForAsync(() => { - spyOn(graphVisualizerService, 'getQuerytype').and.returnValue('other'); + mockGraphVisualizerServiceGetQueryTypeSpy.and.returnValue('other'); // Perform query component.performQuery(); diff --git a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts index d7befd55f2..82775723e4 100644 --- a/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts +++ b/src/app/views/edition-view/edition-outlets/edition-complex/edition-detail/edition-graph/graph-visualizer/graph-visualizer.component.ts @@ -147,6 +147,7 @@ export class GraphVisualizerComponent implements OnInit { ? this.queryList.find(q => query.queryLabel === q.queryLabel && query.queryType === q.queryType) || query : this.queryList[0]; this.query = { ...resetted }; + this.performQuery(); } From 58349acf33dcc966f846ca1730dd7e2d49c1c2ec Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 10 Jul 2022 17:01:12 +0200 Subject: [PATCH 026/122] test(shared): add tests for ViewHandleButtonGroup --- .../table-pagination.component.spec.ts | 2 +- ...view-handle-button-group.component.spec.ts | 505 +++++++++++++++++- .../view-handle-button-group.component.ts | 3 +- 3 files changed, 502 insertions(+), 8 deletions(-) diff --git a/src/app/shared/table/table-pagination/table-pagination.component.spec.ts b/src/app/shared/table/table-pagination/table-pagination.component.spec.ts index 2fbf6ce4f4..a7ad277b45 100644 --- a/src/app/shared/table/table-pagination/table-pagination.component.spec.ts +++ b/src/app/shared/table/table-pagination/table-pagination.component.spec.ts @@ -12,7 +12,7 @@ import { import { TablePaginationComponent } from './table-pagination.component'; -describe('TablePaginationComponent', () => { +describe('TablePaginationComponent (DONE)', () => { let component: TablePaginationComponent; let fixture: ComponentFixture; let compDe: DebugElement; diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts b/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts index c91d95fe69..d7e78fbbe3 100644 --- a/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts +++ b/src/app/shared/view-handle-button-group/view-handle-button-group.component.spec.ts @@ -1,16 +1,35 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { DebugElement, SimpleChange } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { FontAwesomeTestingModule } from '@fortawesome/angular-fontawesome/testing'; +import { faDiagramProject, faGripHorizontal, faTable } from '@fortawesome/free-solid-svg-icons'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import Spy = jasmine.Spy; + +import { expectSpyCall, getAndExpectDebugElementByCss } from '@testing/expect-helper'; + +import { ViewHandle, ViewHandleTypes } from './view-handle.model'; import { ViewHandleButtonGroupComponent } from './view-handle-button-group.component'; -describe('ViewHandleButtonGroupComponent', () => { +describe('ViewHandleButtonGroupComponent (DONE)', () => { let component: ViewHandleButtonGroupComponent; let fixture: ComponentFixture; + let compDe: DebugElement; + + let expectedViewHandles: ViewHandle[]; + let expectedSelectedViewType: ViewHandleTypes; + + let createFormGroupSpy: Spy; + let listenToUserInputChangeSpy: Spy; + let onViewChangeSpy: Spy; + let viewChangeRequestSpy: Spy; + let viewHandleTrackerSpy: Spy; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ReactiveFormsModule], - declarations: [ViewHandleButtonGroupComponent], + imports: [FontAwesomeTestingModule, ReactiveFormsModule], + declarations: [ViewHandleButtonGroupComponent, NgbTooltip], providers: [FormBuilder], }).compileComponents(); }); @@ -18,10 +37,486 @@ describe('ViewHandleButtonGroupComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ViewHandleButtonGroupComponent); component = fixture.componentInstance; - fixture.detectChanges(); + compDe = fixture.debugElement; + + // Test data + expectedViewHandles = [ + new ViewHandle('Graph view', ViewHandleTypes.GRAPH, faDiagramProject), + new ViewHandle('Table view', ViewHandleTypes.TABLE, faTable), + new ViewHandle('Grid view', ViewHandleTypes.GRID, faGripHorizontal), + ]; + expectedSelectedViewType = ViewHandleTypes.GRAPH; + + // Spies on component functions + // `.and.callThrough` will track the spy down the nested describes, see + // https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3Eand.callThrough%3C/code%3E + createFormGroupSpy = spyOn(component, 'createFormGroup').and.callThrough(); + listenToUserInputChangeSpy = spyOn(component, 'listenToUserInputChange').and.callThrough(); + onViewChangeSpy = spyOn(component, 'onViewChange').and.callThrough(); + viewChangeRequestSpy = spyOn(component.viewChangeRequest, 'emit').and.callThrough(); + viewHandleTrackerSpy = spyOn(component, 'viewHandleTracker').and.callThrough(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('BEFORE initial data binding', () => { + it('should not have a view handle control form', () => { + expect(component.viewHandleControlForm).toBeUndefined(); + }); + + it('should not have a selected view type', () => { + expect(component.selectedViewType).toBeUndefined(); + }); + + it('should not have a list of view handles', () => { + expect(component.viewHandles).toBeUndefined(); + }); + + describe('VIEW', () => { + it('should have a div.awg-view-handle-btn-group', () => { + getAndExpectDebugElementByCss(compDe, 'div.awg-view-handle-btn-group', 1, 1); + }); + + it('should have a form in div.awg-view-handle-btn-group', () => { + const divDe = getAndExpectDebugElementByCss(compDe, 'div.awg-view-handle-btn-group', 1, 1); + + getAndExpectDebugElementByCss(divDe[0], 'form', 1, 1); + }); + + it('should have another div.btn-group in form', () => { + const formDe = getAndExpectDebugElementByCss(compDe, 'div.awg-view-handle-btn-group > form', 1, 1); + + getAndExpectDebugElementByCss(formDe[0], 'div.btn-group', 1, 1); + }); + + it('should not have any input elements in div.btn-group', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + + getAndExpectDebugElementByCss(divDe[0], 'input', 0, 0); + }); + + it('should not have any label elements in div.btn-group', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + + getAndExpectDebugElementByCss(divDe[0], 'label', 0, 0); + }); + }); + }); + + describe('AFTER initial data binding', () => { + beforeEach(() => { + // Simulate the parent component setting the data + component.viewHandles = expectedViewHandles; + component.selectedViewType = expectedSelectedViewType; + + // Trigger initial data binding + fixture.detectChanges(); + }); + + it('should trigger the `createFormGroup()` method with selected view type', () => { + expectSpyCall(createFormGroupSpy, 1, expectedSelectedViewType); + }); + + describe('#createFormGroup()', () => { + it('should have a `createFormGroup()` method', () => { + expect(component.createFormGroup).toBeDefined(); + }); + + it('should trigger on init', () => { + expectSpyCall(createFormGroupSpy, 1, expectedSelectedViewType); + }); + + it('... should trigger on changes of selectedViewType', () => { + expectSpyCall(createFormGroupSpy, 1, ViewHandleTypes.GRAPH); + + // Directly trigger ngOnChanges + component.selectedViewType = ViewHandleTypes.GRID; + component.ngOnChanges({ + selectedViewType: new SimpleChange(component.selectedViewType, [component.selectedViewType], false), + }); + + expectSpyCall(createFormGroupSpy, 2, ViewHandleTypes.GRID); + }); + + it('... should only trigger on changes of selectedViewType if not first change', () => { + expectSpyCall(createFormGroupSpy, 1, ViewHandleTypes.GRAPH); + + // Directly trigger ngOnChanges + component.selectedViewType = ViewHandleTypes.GRID; + component.ngOnChanges({ + selectedViewType: new SimpleChange(component.selectedViewType, [ViewHandleTypes.GRID], true), + }); + + expectSpyCall(createFormGroupSpy, 1, (component.selectedViewType = ViewHandleTypes.GRAPH)); + }); + + it('should create the viewHandleControlForm', () => { + expect(component.viewHandleControlForm).toBeDefined(); + expect(component.viewHandleControlForm).toBeInstanceOf(FormGroup); + expect(component.viewHandleControlForm.controls).toBeDefined(); + }); + + it('should create the viewHandleControlForm with correct viewHandleControl', () => { + expect(component.viewHandleControlForm.controls).toBeDefined(); + + expect(component.viewHandleControlForm.controls['viewHandleControl']).toBeDefined(); + expect(component.viewHandleControlForm.controls['viewHandleControl']).toBeInstanceOf(FormControl); + }); + + it('should create the viewHandleControlForm with correct viewHandleControl value', () => { + expect(component.viewHandleControlForm.controls['viewHandleControl'].value).toBeTruthy(); + expect(component.viewHandleControlForm.controls['viewHandleControl'].value).toBe( + expectedSelectedViewType + ); + }); + + it('should get the viewHandleControl from its getter', () => { + expect(component.viewHandleControl).toBeDefined(); + expect(component.viewHandleControl).toBeInstanceOf(FormControl); + + expect(component.viewHandleControl.value).toBeTruthy(); + expect(component.viewHandleControl.value).toBe(expectedSelectedViewType); + }); + + it('should trigger the `listenToUserInputChange()` method', () => { + expectSpyCall(listenToUserInputChangeSpy, 1); + + // Trigger the `listenToUserInputChange()` method + component.createFormGroup(ViewHandleTypes.TABLE); + fixture.detectChanges(); + + expectSpyCall(listenToUserInputChangeSpy, 2); + }); + }); + + describe('#listenToUserInputChange()', () => { + it('should have a `listenToUserInputChange()` method', () => { + expect(component.listenToUserInputChange).toBeDefined(); + }); + + it('should trigger the `onViewChange()` method when viewHandle controls changes value', () => { + // Trigger the value change + component.viewHandleControl.setValue(ViewHandleTypes.TABLE); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.TABLE); + + // Trigger the value change + component.viewHandleControl.setValue(ViewHandleTypes.GRAPH); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 2, ViewHandleTypes.GRAPH); + + // Trigger the value change + component.viewHandleControl.setValue(ViewHandleTypes.GRID); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 3, ViewHandleTypes.GRID); + }); + + it('should trigger the `onViewChange()` method by by change event from GRAPH radio button', () => { + const inputDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group > input', + 3, + 3 + ); + const inputEl = inputDes[0].nativeElement; + + inputEl.dispatchEvent(new Event('change')); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.GRAPH); + }); + + it('should trigger the `onViewChange()` method by by change event from TABLE radio button', () => { + const inputDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group > input', + 3, + 3 + ); + const inputEl = inputDes[1].nativeElement; + + inputEl.dispatchEvent(new Event('change')); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.TABLE); + }); + + it('should trigger the `onViewChange()` method by change event from GRID radio button', () => { + const inputDes = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group > input', + 3, + 3 + ); + const inputEl = inputDes[2].nativeElement; + + inputEl.dispatchEvent(new Event('change')); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.GRID); + }); + + it('should not trigger the `onViewChange()` method when component is destroyed', () => { + // Trigger the value change + component.viewHandleControl.setValue(ViewHandleTypes.TABLE); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.TABLE); + + // Destroy the component + fixture.destroy(); + + // Trigger the value change + component.viewHandleControl.setValue(ViewHandleTypes.GRAPH); + fixture.detectChanges(); + + expectSpyCall(onViewChangeSpy, 1, ViewHandleTypes.TABLE); + }); + }); + + describe('#onViewChange()', () => { + it('should have a `onViewChange()` method', () => { + expect(component.onViewChange).toBeDefined(); + }); + + describe('... should not do anything if ', () => { + it('... view is undefined', () => { + component.onViewChange(undefined); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 0); + }); + + it('... view is null', () => { + component.onViewChange(null); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 0); + }); + + it('... view has type `never`', () => { + let expectedView: never; + + component.onViewChange(expectedView); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 0); + }); + }); + + it('should emit a given GRAPH view', () => { + const expectedView = ViewHandleTypes.GRAPH; + + component.onViewChange(expectedView); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 1, 'graph'); + }); + + it('should emit a given TABLE view', () => { + const expectedView = ViewHandleTypes.TABLE; + + component.onViewChange(expectedView); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 1, 'table'); + }); + + it('should emit a given GRID view', () => { + const expectedView = ViewHandleTypes.GRID; + + component.onViewChange(expectedView); + fixture.detectChanges(); + + expectSpyCall(viewChangeRequestSpy, 1, 'grid'); + }); + }); + + describe('#viewHandleTracker()', () => { + it('should have a `viewHandleTracker()` method', () => { + expect(component.viewHandleTracker).toBeDefined(); + }); + + it('should return the type of a given view handle', () => { + expect(component.viewHandleTracker(0, expectedViewHandles[0])).toBe(ViewHandleTypes.GRAPH); + expect(component.viewHandleTracker(1, expectedViewHandles[1])).toBe(ViewHandleTypes.TABLE); + }); + }); + + describe('VIEW', () => { + it('should have as many radio elements (input.btn-check) in div.btn-group as viewHandles given', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + + const inputDes = getAndExpectDebugElementByCss( + divDe[0], + 'input.btn-check', + expectedViewHandles.length, + expectedViewHandles.length + ); + + for (let i = 0; i < expectedViewHandles.length; i++) { + expect(inputDes[i].nativeElement.type).toBe('radio'); + } + }); + + it('should set the value of the input element to the viewHandle type', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + const inputDes = getAndExpectDebugElementByCss( + divDe[0], + 'input', + expectedViewHandles.length, + expectedViewHandles.length + ); + + for (let i = 0; i < expectedViewHandles.length; i++) { + expect(inputDes[i].attributes['ng-reflect-value']).toBeTruthy(); + expect(inputDes[i].attributes['ng-reflect-value']) + .withContext(`should be ${expectedViewHandles[i].type}`) + .toBe(expectedViewHandles[i].type); + } + }); + + it('should set the id of the input element to `{viewHandle.type}-view-button`', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + const inputDes = getAndExpectDebugElementByCss( + divDe[0], + 'input', + expectedViewHandles.length, + expectedViewHandles.length + ); + + const inputEl1 = inputDes[0].nativeElement; + const inputEl2 = inputDes[1].nativeElement; + const inputEl3 = inputDes[2].nativeElement; + + expect(inputEl1.id).toBeTruthy(); + expect(inputEl1.id) + .withContext(`should be '${expectedViewHandles}-view-button'`) + .toBe(`${expectedViewHandles[0].type}-view-button`); + + expect(inputEl2.id).toBeTruthy(); + expect(inputEl2.id) + .withContext(`should be '${expectedViewHandles}-view-button'`) + .toBe(`${expectedViewHandles[1].type}-view-button`); + + expect(inputEl3.id).toBeTruthy(); + expect(inputEl3.id) + .withContext(`should be '${expectedViewHandles}-view-button'`) + .toBe(`${expectedViewHandles[2].type}-view-button`); + }); + + it('should have as many label elements in div.btn-group as viewHandles given', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + + getAndExpectDebugElementByCss( + divDe[0], + 'label', + expectedViewHandles.length, + expectedViewHandles.length + ); + }); + + it('should have as many icon elements in div.btn-group > label as viewHandles given', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + const iconDes = getAndExpectDebugElementByCss( + divDe[0], + 'label > fa-icon', + expectedViewHandles.length, + expectedViewHandles.length + ); + + const iconDeChild1 = iconDes[0].children[0]; + const iconDeChild2 = iconDes[1].children[0]; + const iconDeChild3 = iconDes[2].children[0]; + + expect(iconDeChild1.classes['fa-diagram-project']).toBeTruthy(); + expect(iconDeChild2.classes['fa-table']).toBeTruthy(); + expect(iconDeChild3.classes['fa-grip']).toBeTruthy(); + }); + + it('should set the label for the correct input', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + const labelDes = getAndExpectDebugElementByCss( + divDe[0], + 'label', + expectedViewHandles.length, + expectedViewHandles.length + ); + + for (let i = 0; i < expectedViewHandles.length; i++) { + expect(labelDes[i].attributes['for']).toBeTruthy(); + expect(labelDes[i].attributes['for']) + .withContext(`should be ${expectedViewHandles[i].type}-view-button`) + .toBe(`${expectedViewHandles[i].type}-view-button`); + } + }); + + it('should display tooltip with `{type} view` for each view handle', () => { + const divDe = getAndExpectDebugElementByCss( + compDe, + 'div.awg-view-handle-btn-group > form > div.btn-group', + 1, + 1 + ); + const labelDes = getAndExpectDebugElementByCss( + divDe[0], + 'label', + expectedViewHandles.length, + expectedViewHandles.length + ); + + for (let i = 0; i < expectedViewHandles.length; i++) { + expect(labelDes[i].attributes['ng-reflect-ngb-tooltip']).toBeTruthy(); + expect(labelDes[i].attributes['ng-reflect-ngb-tooltip']) + .withContext(`should be '${expectedViewHandles[i].type} view'`) + .toBe(expectedViewHandles[i].type + ' view'); + } + }); + }); + }); }); diff --git a/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts b/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts index 87873aed71..46e03c54c2 100644 --- a/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts +++ b/src/app/shared/view-handle-button-group/view-handle-button-group.component.ts @@ -79,7 +79,6 @@ export class ViewHandleButtonGroupComponent implements OnInit, OnChanges, OnDest * when initializing the component. */ ngOnInit(): void { - console.log('ViewHandleButtonGroupComponent got view', this.selectedViewType); this.createFormGroup(this.selectedViewType); } @@ -109,7 +108,7 @@ export class ViewHandleButtonGroupComponent implements OnInit, OnChanges, OnDest */ createFormGroup(view: ViewHandleTypes): void { this.viewHandleControlForm = this.formBuilder.group({ - viewHandleControl: ViewHandleTypes[view], + viewHandleControl: view, }); this.listenToUserInputChange(); From 89bf082fec64bb88822b4aab0e484935c0d8391d Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 10 Jul 2022 19:22:52 +0200 Subject: [PATCH 027/122] refactor(search): use ViewHandleTypes for refactored SearchParams --- .../search-panel/search-panel.component.ts | 85 ++++++++----------- .../search-result-list.component.ts | 23 ++--- src/app/views/data-view/models/index.ts | 3 +- .../data-view/models/search-params.model.ts | 29 ++++--- .../data-view/services/data-api.service.ts | 22 ++--- 5 files changed, 78 insertions(+), 84 deletions(-) diff --git a/src/app/views/data-view/data-outlets/search-panel/search-panel.component.ts b/src/app/views/data-view/data-outlets/search-panel/search-panel.component.ts index d90594ce9d..b2aa18ad4f 100644 --- a/src/app/views/data-view/data-outlets/search-panel/search-panel.component.ts +++ b/src/app/views/data-view/data-outlets/search-panel/search-panel.component.ts @@ -8,15 +8,9 @@ import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'; import { ConversionService, DataStreamerService, LoadingService } from '@awg-core/services'; import { SearchResponseJson } from '@awg-shared/api-objects'; - +import { ViewHandleTypes } from '@awg-shared/view-handle-button-group/view-handle.model'; import { DataApiService } from '@awg-views/data-view/services'; -import { - SearchParams, - SearchResultsViewTypes, - SearchResponseWithQuery, - SearchQuery, - ExtendedSearchParams, -} from '@awg-views/data-view/models'; +import { SearchParams, SearchResponseWithQuery, SearchQuery, ExtendedSearchParams } from '@awg-views/data-view/models'; /** * The SearchPanel component. @@ -235,12 +229,12 @@ export class SearchPanelComponent implements OnInit, OnDestroy { // View has not changed this.viewChanged = false; - this.searchParams = { - query: this.searchParams.query, - nRows: this.searchParams.nRows, - startAt: requestedStartAt, - view: this.searchParams.view, - }; + this.searchParams = new SearchParams( + this.searchParams.query, + this.searchParams.nRows, + requestedStartAt, + this.searchParams.viewType + ); // Route to new params this._routeToSelf(this.searchParams); } @@ -262,12 +256,12 @@ export class SearchPanelComponent implements OnInit, OnDestroy { // View has not changed this.viewChanged = false; - this.searchParams = { - query: this.searchParams.query, - nRows: requestedRows, - startAt: '0', - view: this.searchParams.view, - }; + this.searchParams = new SearchParams( + this.searchParams.query, + requestedRows, + '0', + this.searchParams.viewType + ); // Route to new params this._routeToSelf(this.searchParams); @@ -290,12 +284,12 @@ export class SearchPanelComponent implements OnInit, OnDestroy { // View has not changed this.viewChanged = false; - this.searchParams = { - query: requestedQuery, - nRows: this.searchParams.nRows, - startAt: this.searchParams.startAt, - view: this.searchParams.view, - }; + this.searchParams = new SearchParams( + requestedQuery, + this.searchParams.nRows, + this.searchParams.startAt, + this.searchParams.viewType + ); // Route to new search params this._routeToSelf(this.searchParams); @@ -330,21 +324,21 @@ export class SearchPanelComponent implements OnInit, OnDestroy { * after a view change request and triggers the * {@link _routeToSelf} method. * - * @param {string} requestedView The given view. + * @param {ViewHandleTypes} requestedViewType The given view type. * * @returns {void} Sets the search params and routes to itself. */ - onViewChange(requestedView: string): void { - if (requestedView !== this.searchParams.view) { + onViewChange(requestedViewType: ViewHandleTypes): void { + if (requestedViewType !== this.searchParams.viewType) { // View has changed this.viewChanged = true; - this.searchParams = { - query: this.searchParams.query, - nRows: this.searchParams.nRows, - startAt: this.searchParams.startAt, - view: SearchResultsViewTypes[requestedView], - }; + this.searchParams = new SearchParams( + this.searchParams.query, + this.searchParams.nRows, + this.searchParams.startAt, + requestedViewType + ); // Route to new params this._routeToSelf(this.searchParams); @@ -371,12 +365,7 @@ export class SearchPanelComponent implements OnInit, OnDestroy { query.compop = []; query.searchval = []; } - this.searchParams = { - query: query, - nRows: '25', - startAt: '0', - view: SearchResultsViewTypes.table, - }; + this.searchParams = new SearchParams(query, '25', '0', ViewHandleTypes.TABLE); } /** @@ -429,12 +418,12 @@ export class SearchPanelComponent implements OnInit, OnDestroy { } // Update search params (immutable) - this.searchParams = { - query: query, - nRows: params.get('nrows') || this.searchParams.nRows, - startAt: params.get('startAt') || this.searchParams.startAt, - view: SearchResultsViewTypes[params.get('view')] || this.searchParams.view, - }; + this.searchParams = new SearchParams( + query, + params.get('nrows') || this.searchParams.nRows, + params.get('startAt') || this.searchParams.startAt, + ViewHandleTypes[params.get('view')] || this.searchParams.viewType + ); if (routing) { this._routeToSelf(this.searchParams); @@ -533,7 +522,7 @@ export class SearchPanelComponent implements OnInit, OnDestroy { } qp['nrows'] = sp.nRows; qp['startAt'] = sp.startAt; - qp['view'] = sp.view; + qp['view'] = sp.viewType; return qp; } diff --git a/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.ts b/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.ts index 9f48260f3d..b040122052 100644 --- a/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.ts +++ b/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.ts @@ -7,10 +7,10 @@ import { takeUntil } from 'rxjs/operators'; import { faGripHorizontal, faTable } from '@fortawesome/free-solid-svg-icons'; -import { SearchInfo } from '@awg-side-info/side-info-models'; -import { SearchParams, SearchResultsViewTypes, SearchResponseWithQuery } from '@awg-views/data-view/models'; - import { ConversionService, DataStreamerService, SideInfoService } from '@awg-core/services'; +import { ViewHandle, ViewHandleTypes } from '@awg-shared/view-handle-button-group/view-handle.model'; +import { SearchInfo } from '@awg-side-info/side-info-models'; +import { SearchParams, SearchResponseWithQuery } from '@awg-views/data-view/models'; /** * The SearchResultList component. @@ -68,7 +68,7 @@ export class SearchResultListComponent implements OnInit, OnDestroy { * It keeps an event emitter for the selected view type of the search result list. */ @Output() - viewChangeRequest: EventEmitter = new EventEmitter(); + viewChangeRequest: EventEmitter = new EventEmitter(); /** * Public variable: errorMessage. @@ -185,9 +185,9 @@ export class SearchResultListComponent implements OnInit, OnDestroy { this.getSearchResponseWithQueryData(); if ( - this.searchParams.view && - (this.searchParams.view === SearchResultsViewTypes.table || - this.searchParams.view === SearchResultsViewTypes.grid) + this.searchParams.viewType && + (this.searchParams.viewType === ViewHandleTypes.TABLE || + this.searchParams.viewType === ViewHandleTypes.GRID) ) { this.createFormGroup(this.searchParams.view); } @@ -251,7 +251,7 @@ export class SearchResultListComponent implements OnInit, OnDestroy { * @returns {boolean} The boolean value of the check result. */ isGridView(): boolean { - return this.searchParams.view === SearchResultsViewTypes.grid; + return this.searchParams.viewType === ViewHandleTypes.GRID; } /** @@ -316,11 +316,14 @@ export class SearchResultListComponent implements OnInit, OnDestroy { * It emits the new view type * to the {@link viewChangeRequest}. * - * @param {string} view The given view type. + * @param {ViewHandleTypes} view The given view type. * * @returns {void} Emits the new view type. */ - onViewChange(view: string): void { + onViewChange(view: ViewHandleTypes): void { + if (!view) { + return; + } this.viewChangeRequest.emit(view); } diff --git a/src/app/views/data-view/models/index.ts b/src/app/views/data-view/models/index.ts index 324fcb318a..f4a8320178 100644 --- a/src/app/views/data-view/models/index.ts +++ b/src/app/views/data-view/models/index.ts @@ -15,7 +15,7 @@ import { ResourceDetailImage } from './resource-detail-image.model'; import { ResourceDetailIncomingLink } from './resource-detail-incoming-link.model'; import { ResourceDetailProperty } from './resource-detail-property.model'; import { ResourceDetailGroupedIncomingLinks } from './resource-detail-grouped-incoming-links.model'; -import { ExtendedSearchParams, SearchQuery, SearchParams, SearchResultsViewTypes } from './search-params.model'; +import { ExtendedSearchParams, SearchQuery, SearchParams } from './search-params.model'; import { SearchResponseWithQuery } from './search-response-with-query.model'; import { SearchCompop, SEARCH_COMPOP_SETS_LIST } from './search-compop.model'; import { VALUETYPE_LIST } from './value-type.model'; @@ -33,7 +33,6 @@ export { ResourceDetailGroupedIncomingLinks, SearchQuery, SearchParams, - SearchResultsViewTypes, SearchResponseWithQuery, SearchCompop, SEARCH_COMPOP_SETS_LIST, diff --git a/src/app/views/data-view/models/search-params.model.ts b/src/app/views/data-view/models/search-params.model.ts index 5ca2545844..db460680df 100644 --- a/src/app/views/data-view/models/search-params.model.ts +++ b/src/app/views/data-view/models/search-params.model.ts @@ -1,12 +1,4 @@ -/** - * The SearchResultsViewTypes enumeration. - * - * It stores the possible view types for search results. - */ -export enum SearchResultsViewTypes { - table = 'table', - grid = 'grid', -} +import { ViewHandleTypes } from '@awg-shared/view-handle-button-group/view-handle.model'; /** * Describes a type that can be either string or ExtendedSearchParams object. @@ -36,9 +28,24 @@ export class SearchParams { startAt: string; /** - * The requested view type ('table', 'grid'). + * The requested view type. + */ + viewType: ViewHandleTypes; + + /** + * Constructor of the SearchParams class. + * + * @param {SearchQuery} query The search query (string or ExtendedSearchParams). + * @param {string} nRows The number of rows to return per result list page. + * @param {string} startAt The position in the result list to start at. + * @param {ViewHandleTypes} viewType The requested view type. */ - view: SearchResultsViewTypes; + constructor(query: SearchQuery, nRows: string, startAt: string, viewType: ViewHandleTypes) { + this.query = query; + this.nRows = nRows; + this.startAt = startAt; + this.viewType = viewType; + } } /** diff --git a/src/app/views/data-view/services/data-api.service.ts b/src/app/views/data-view/services/data-api.service.ts index 8181c47aaf..77be5f015a 100644 --- a/src/app/views/data-view/services/data-api.service.ts +++ b/src/app/views/data-view/services/data-api.service.ts @@ -12,13 +12,8 @@ import { ResourceTypesInVocabularyResponseJson, SearchResponseJson, } from '@awg-shared/api-objects'; -import { - IResourceDataResponse, - ResourceData, - ResourceDetail, - SearchParams, - SearchResultsViewTypes, -} from '@awg-views/data-view/models'; +import { ViewHandleTypes } from '@awg-shared/view-handle-button-group/view-handle.model'; +import { IResourceDataResponse, ResourceData, ResourceDetail, SearchParams } from '@awg-views/data-view/models'; /** * The DataApi service. @@ -196,12 +191,12 @@ export class DataApiService extends ApiService { } // Default values - const sp: SearchParams = { - query: searchParams.query, - nRows: searchParams.nRows || '-1', - startAt: searchParams.startAt || '0', - view: searchParams.view || SearchResultsViewTypes.table, - }; + const sp: SearchParams = new SearchParams( + searchParams.query, + searchParams.nRows || '-1', + searchParams.startAt || '0', + searchParams.viewType || ViewHandleTypes.TABLE + ); // Cold request to API const searchData$: Observable = this._getResourceDataResponseFromApi( @@ -299,6 +294,7 @@ export class DataApiService extends ApiService { * * @param {*} responseJsonType The given json type of the API response. * @param {string} id The given id of a resource. + * @param {SearchParams} [searchParams] The given search params. * * @returns {Observable} The observable of the HTTP response. */ From 1a313f3234e6cda50da231808d5200a5d5227266 Mon Sep 17 00:00:00 2001 From: musicEnfanthen Date: Sun, 10 Jul 2022 19:24:34 +0200 Subject: [PATCH 028/122] fix(edition): use ViewHandleButtonGroup in SearchResultList --- .../search-result-list.component.html | 39 ++-------- .../search-result-list.component.ts | 76 +++++++------------ 2 files changed, 34 insertions(+), 81 deletions(-) diff --git a/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.html b/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.html index 494573344d..4846007009 100644 --- a/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.html +++ b/src/app/views/data-view/data-outlets/search-panel/search-result-list/search-result-list.component.html @@ -12,39 +12,14 @@
-
-
- - + + - - -
-
-