This commit is contained in:
Aman Budgujar
2023-03-02 18:18:52 +05:30
74 changed files with 373 additions and 210 deletions

View File

@@ -369,3 +369,8 @@ vocabularies:
- filter: 'subject'
vocabulary: 'srsc'
enabled: true
# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query.
comcolSelectionSort:
sortField: 'dc.title'
sortDirection: 'ASC'

View File

@@ -8,7 +8,7 @@
<button class="mr-auto btn btn-success addEPerson-button"
(click)="isEPersonFormShown = true">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">{{labelPrefix + 'button.add' | translate}}</span>
<span class="d-none d-sm-inline ml-1">{{labelPrefix + 'button.add' | translate}}</span>
</button>
</div>
</div>
@@ -30,7 +30,7 @@
<div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" attr.aria-label="{{labelPrefix + 'search.placeholder' | translate}}"
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">
@@ -72,13 +72,13 @@
<td>{{epersonDto.eperson.email}}</td>
<td>
<div class="btn-group edit-field">
<button class="delete-button" (click)="toggleEditEPerson(epersonDto.eperson)"
<button (click)="toggleEditEPerson(epersonDto.eperson)"
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: {name: epersonDto.eperson.name} }}">
<i class="fas fa-edit fa-fw"></i>
</button>
<button [disabled]="!epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
class="btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: {name: epersonDto.eperson.name} }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>

View File

@@ -1,7 +1,7 @@
import { Router } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
@@ -42,6 +42,7 @@ describe('EPeopleRegistryComponent', () => {
let paginationService;
beforeEach(waitForAsync(() => {
jasmine.getEnv().allowRespy(true);
mockEPeople = [EPersonMock, EPersonMock2];
ePersonDataServiceStub = {
activeEPerson: null,
@@ -260,17 +261,16 @@ describe('EPeopleRegistryComponent', () => {
describe('delete EPerson button when the isAuthorized returns false', () => {
let ePeopleDeleteButton;
beforeEach(() => {
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(false)
});
spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false));
component.initialisePage();
fixture.detectChanges();
});
it('should be disabled', () => {
ePeopleDeleteButton = fixture.debugElement.queryAll(By.css('#epeople tr td div button.delete-button'));
ePeopleDeleteButton.forEach((deleteButton) => {
ePeopleDeleteButton.forEach((deleteButton: DebugElement) => {
expect(deleteButton.nativeElement.disabled).toBe(true);
});
});
});
});

View File

@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
@@ -37,10 +37,10 @@ describe('MembersListComponent', () => {
let ePersonDataServiceStub: any;
let groupsDataServiceStub: any;
let activeGroup;
let allEPersons;
let allGroups;
let epersonMembers;
let subgroupMembers;
let allEPersons: EPerson[];
let allGroups: Group[];
let epersonMembers: EPerson[];
let subgroupMembers: Group[];
let paginationService;
beforeEach(waitForAsync(() => {
@@ -53,7 +53,7 @@ describe('MembersListComponent', () => {
activeGroup: activeGroup,
epersonMembers: epersonMembers,
subgroupMembers: subgroupMembers,
findListByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
},
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
@@ -147,6 +147,7 @@ describe('MembersListComponent', () => {
});
afterEach(fakeAsync(() => {
fixture.destroy();
fixture.debugElement.nativeElement.remove();
flush();
component = null;
fixture.debugElement.nativeElement.remove();
@@ -168,12 +169,19 @@ describe('MembersListComponent', () => {
describe('search', () => {
describe('when searching without query', () => {
let epersonsFound;
let epersonsFound: DebugElement[];
beforeEach(fakeAsync(() => {
spyOn(component, 'isMemberOfGroup').and.callFake((ePerson: EPerson) => {
return observableOf(activeGroup.epersons.includes(ePerson));
});
component.search({ scope: 'metadata', query: '' });
tick();
fixture.detectChanges();
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
// Stop using the fake spy function (because otherwise the clicking on the buttons will not change anything
// because they don't change the value of activeGroup.epersons)
jasmine.getEnv().allowRespy(true);
spyOn(component, 'isMemberOfGroup').and.callThrough();
}));
it('should display all epersons', () => {
@@ -182,62 +190,56 @@ describe('MembersListComponent', () => {
describe('if eperson is already a eperson', () => {
it('should have delete button, else it should have add button', () => {
activeGroup.epersons.map((eperson: EPerson) => {
epersonsFound.map((foundEPersonRowElement) => {
if (foundEPersonRowElement.debugElement !== undefined) {
const epersonId = foundEPersonRowElement.debugElement.query(By.css('td:first-child'));
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
if (epersonId.nativeElement.textContent === eperson.id) {
expect(addButton).toBeUndefined();
expect(deleteButton).toBeDefined();
const memberIds: string[] = activeGroup.epersons.map((ePerson: EPerson) => ePerson.id);
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const epersonId: DebugElement = foundEPersonRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
if (memberIds.includes(epersonId.nativeElement.textContent)) {
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
} else {
expect(deleteButton).toBeUndefined();
expect(addButton).toBeDefined();
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
}
}
});
});
});
});
describe('if first add button is pressed', () => {
beforeEach(fakeAsync(() => {
const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
addButton.nativeElement.click();
tick();
fixture.detectChanges();
}));
it('all groups in search member of selected group', () => {
it('then all the ePersons are member of the active group', () => {
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
expect(epersonsFound.length).toEqual(2);
epersonsFound.map((foundEPersonRowElement) => {
if (foundEPersonRowElement.debugElement !== undefined) {
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeUndefined();
expect(deleteButton).toBeDefined();
}
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
});
});
});
describe('if first delete button is pressed', () => {
beforeEach(fakeAsync(() => {
const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt'));
addButton.nativeElement.click();
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt'));
deleteButton.nativeElement.click();
tick();
fixture.detectChanges();
}));
it('first eperson in search delete button, because now member', () => {
it('then no ePerson is member of the active group', () => {
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
epersonsFound.map((foundEPersonRowElement) => {
if (foundEPersonRowElement.debugElement !== undefined) {
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
expect(deleteButton).toBeUndefined();
expect(addButton).toBeDefined();
}
expect(epersonsFound.length).toEqual(2);
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
});
});
});

View File

@@ -249,6 +249,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
* @param ePerson EPerson we want to delete as member from group that is currently being edited
*/
deleteMemberFromGroup(ePerson: EpersonDtoModel) {
ePerson.memberOfGroup = false;
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup != null) {
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson);

View File

@@ -65,7 +65,7 @@
<i class="fas fa-trash-alt fa-fw"></i>
</button>
<p *ngIf="(isActiveGroup(group) | async)">{{ messagePrefix + '.table.edit.currentGroup' | translate }}</p>
<span *ngIf="(isActiveGroup(group) | async)">{{ messagePrefix + '.table.edit.currentGroup' | translate }}</span>
<button *ngIf="!(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
(click)="addSubgroupToGroup(group)"

View File

@@ -1,14 +1,6 @@
import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
ComponentFixture,
fakeAsync,
flush,
inject,
TestBed,
tick,
waitForAsync
} from '@angular/core/testing';
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
import { Router } from '@angular/router';
@@ -46,8 +38,8 @@ describe('SubgroupsListComponent', () => {
let ePersonDataServiceStub: any;
let groupsDataServiceStub: any;
let activeGroup;
let subgroups;
let allGroups;
let subgroups: Group[];
let allGroups: Group[];
let routerStub;
let paginationService;
@@ -65,7 +57,7 @@ describe('SubgroupsListComponent', () => {
getSubgroups(): Group {
return this.activeGroup;
},
findListByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
findListByHref(_href: string): Observable<RemoteData<PaginatedList<Group>>> {
return this.subgroups$.pipe(
map((currentGroups: Group[]) => {
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
@@ -133,6 +125,7 @@ describe('SubgroupsListComponent', () => {
});
afterEach(fakeAsync(() => {
fixture.destroy();
fixture.debugElement.nativeElement.remove();
flush();
component = null;
}));
@@ -152,7 +145,7 @@ describe('SubgroupsListComponent', () => {
});
describe('if first group delete button is pressed', () => {
let groupsFound;
let groupsFound: DebugElement[];
beforeEach(fakeAsync(() => {
const addButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
addButton.triggerEventHandler('click', {
@@ -170,7 +163,7 @@ describe('SubgroupsListComponent', () => {
describe('search', () => {
describe('when searching with empty query', () => {
let groupsFound;
let groupsFound: DebugElement[];
beforeEach(fakeAsync(() => {
component.search({ query: '' });
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
@@ -181,9 +174,9 @@ describe('SubgroupsListComponent', () => {
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
expect(groupsFound.length).toEqual(2);
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
const groupIdsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
const groupIdsFound: DebugElement[] = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
allGroups.map((group: Group) => {
expect(groupIdsFound.find((foundEl) => {
expect(groupIdsFound.find((foundEl: DebugElement) => {
return (foundEl.nativeElement.textContent.trim() === group.uuid);
})).toBeTruthy();
});
@@ -195,30 +188,30 @@ describe('SubgroupsListComponent', () => {
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
const getSubgroups = groupsDataServiceStub.getSubgroups().subgroups;
if (getSubgroups !== undefined && getSubgroups.length > 0) {
groupsFound.map((foundGroupRowElement) => {
if (foundGroupRowElement.debugElement !== undefined) {
const addButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
const deleteButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeUndefined();
expect(deleteButton).toBeDefined();
groupsFound.map((foundGroupRowElement: DebugElement) => {
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
expect(addButton).toBeNull();
if (activeGroup.id === groupId.nativeElement.textContent) {
expect(deleteButton).toBeNull();
} else {
expect(deleteButton).not.toBeNull();
}
});
} else {
getSubgroups.map((group: Group) => {
groupsFound.map((foundGroupRowElement) => {
if (foundGroupRowElement.debugElement !== undefined) {
const groupId = foundGroupRowElement.debugElement.query(By.css('td:first-child'));
const addButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
const deleteButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
if (groupId.nativeElement.textContent === group.id) {
expect(addButton).toBeUndefined();
expect(deleteButton).toBeDefined();
const subgroupIds: string[] = activeGroup.subgroups.map((group: Group) => group.id);
groupsFound.map((foundGroupRowElement: DebugElement) => {
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
if (subgroupIds.includes(groupId.nativeElement.textContent)) {
expect(addButton).toBeNull();
expect(deleteButton).not.toBeNull();
} else {
expect(deleteButton).toBeUndefined();
expect(addButton).toBeDefined();
expect(deleteButton).toBeNull();
expect(addButton).not.toBeNull();
}
}
});
});
}
});

View File

@@ -7,7 +7,7 @@
<button class="mr-auto btn btn-success"
[routerLink]="['newGroup']">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">{{messagePrefix + 'button.add' | translate}}</span>
<span class="d-none d-sm-inline ml-1">{{messagePrefix + 'button.add' | translate}}</span>
</button>
</div>
</div>
@@ -17,7 +17,7 @@
<div class="flex-grow-1 mr-3">
<div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" attr.aria-label="{{messagePrefix + 'search.placeholder' | translate}}"
class="form-control" [attr.aria-label]="messagePrefix + 'search.placeholder' | translate"
[placeholder]="(messagePrefix + 'search.placeholder' | translate)" >
<span class="input-group-append">
<button type="submit" class="search-button btn btn-primary">

View File

@@ -29,7 +29,7 @@
<tbody>
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
<td>
<label>
<label class="mb-0">
<input type="checkbox"
[checked]="isSelected(bitstreamFormat) | async"
(change)="selectBitStreamFormat(bitstreamFormat, $event)"

View File

@@ -29,7 +29,7 @@
<tr *ngFor="let schema of (metadataSchemas | async)?.payload?.page"
[ngClass]="{'table-primary' : isActive(schema) | async}">
<td>
<label>
<label class="mb-0">
<input type="checkbox"
[checked]="isSelected(schema) | async"
(change)="selectMetadataSchema(schema, $event)"

View File

@@ -1,3 +1,7 @@
.selectable-row:hover {
cursor: pointer;
}
:host ::ng-deep #metadatadataschemagroup {
display: flex;
}

View File

@@ -34,14 +34,14 @@
<tr *ngFor="let field of fields?.page"
[ngClass]="{'table-primary' : isActive(field) | async}">
<td>
<label>
<label class="mb-0">
<input type="checkbox"
[checked]="isSelected(field) | async"
(change)="selectMetadataField(field, $event)">
</label>
</td>
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier" class="mb-0">.</label>{{field.qualifier}}</td>
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
</tr>
</tbody>

View File

@@ -1,3 +1,8 @@
.selectable-row:hover {
cursor: pointer;
}
:host ::ng-deep #metadatadatafieldgroup {
display: flex;
flex-wrap: wrap;
}

View File

@@ -52,7 +52,7 @@ describe(`LocaleInterceptor`, () => {
expect(httpRequest.request.headers.has('Accept-Language'));
const lang = httpRequest.request.headers.get('Accept-Language');
expect(lang).toBeDefined();
expect(lang).not.toBeNull();
expect(lang).toBe(languageList.toString());
});

View File

@@ -67,7 +67,7 @@ describe(`XsrfInterceptor`, () => {
expect(httpRequest.request.headers.has('X-XSRF-TOKEN')).toBeTrue();
expect(httpRequest.request.withCredentials).toBeTrue();
const token = httpRequest.request.headers.get('X-XSRF-TOKEN');
expect(token).toBeDefined();
expect(token).not.toBeNull();
expect(token).toBe(testToken.toString());
httpRequest.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText });
@@ -116,11 +116,11 @@ describe(`XsrfInterceptor`, () => {
// ensure mock XSRF token is in response
expect(response.headers.has('DSPACE-XSRF-TOKEN')).toBeTrue();
const token = response.headers.get('DSPACE-XSRF-TOKEN');
expect(token).toBeDefined();
expect(token).not.toBeNull();
expect(token).toBe(mockNewXSRFToken.toString());
// ensure our XSRF-TOKEN cookie exists & has the same value as the new DSPACE-XSRF-TOKEN header
expect(cookieService.get('XSRF-TOKEN')).toBeDefined();
expect(cookieService.get('XSRF-TOKEN')).not.toBeNull();
expect(cookieService.get('XSRF-TOKEN')).toBe(mockNewXSRFToken.toString());
done();
@@ -153,7 +153,7 @@ describe(`XsrfInterceptor`, () => {
expect(error.statusText).toBe(mockErrorText);
// ensure our XSRF-TOKEN cookie exists & has the same value as the new DSPACE-XSRF-TOKEN header
expect(cookieService.get('XSRF-TOKEN')).toBeDefined();
expect(cookieService.get('XSRF-TOKEN')).not.toBeNull();
expect(cookieService.get('XSRF-TOKEN')).toBe(mockNewXSRFToken.toString());
done();

View File

@@ -104,9 +104,13 @@ describe('FullItemPageComponent', () => {
fixture.detectChanges();
}));
afterEach(() => {
fixture.debugElement.nativeElement.remove();
});
it('should display the item\'s metadata', () => {
const table = fixture.debugElement.query(By.css('table'));
for (const metadatum of mockItem.allMetadata([])) {
for (const metadatum of mockItem.allMetadata(Object.keys(mockItem.metadata))) {
expect(table.nativeElement.innerHTML).toContain(metadatum.value);
}
});
@@ -137,7 +141,7 @@ describe('FullItemPageComponent', () => {
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
expect(objectLoader.nativeElement).not.toBeNull();
});
});
describe('when the item is withdrawn and the user is not an admin', () => {
@@ -161,7 +165,7 @@ describe('FullItemPageComponent', () => {
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
expect(objectLoader).not.toBeNull();
});
});
@@ -173,7 +177,7 @@ describe('FullItemPageComponent', () => {
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
expect(objectLoader).not.toBeNull();
});
});
});

View File

@@ -253,7 +253,7 @@ describe('MiradorViewerComponent in development mode', () => {
it('should show message', (() => {
const value = fixture.debugElement
.nativeElement.querySelector('#viewer-message');
expect(value).toBeDefined();
expect(value).not.toBeNull();
}));
});

View File

@@ -3,7 +3,7 @@
<div class="file-section">
<ds-themed-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item">
<span>{{file?.name}}</span>
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
<span> ({{(file?.sizeBytes) | dsFileSize }})</span>
<span *ngIf="!last" innerHTML="{{separator}}"></span>
</ds-themed-file-download-link>
<ds-themed-loading *ngIf="isLoading" message="{{'loading.default' | translate}}" [showMessage]="false"></ds-themed-loading>

View File

@@ -146,7 +146,7 @@ describe('FileSectionComponent', () => {
it('should contain a view less link', () => {
const viewLess = fixture.debugElement.query(By.css('.bitstream-collapse'));
expect(viewLess).toBeDefined();
expect(viewLess).not.toBeNull();
});
it('clicking on the view less link should reset the pages and call getNextPage()', () => {

View File

@@ -44,6 +44,6 @@ describe('ItemPageAbstractFieldComponent', () => {
}));
it('should render a ds-metadata-values', () => {
expect(fixture.debugElement.query(By.css('ds-metadata-values'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-metadata-values'))).not.toBeNull();
});
});

View File

@@ -41,6 +41,7 @@ describe('MenuResolver', () => {
beforeEach(waitForAsync(() => {
menuService = new MenuServiceStub();
spyOn(menuService, 'getMenu').and.returnValue(observableOf(MENU_STATE));
spyOn(menuService, 'addSection');
browseService = jasmine.createSpyObj('browseService', {
getBrowseDefinitions: createSuccessfulRemoteDataObject$(createPaginatedList(BROWSE_DEFINITIONS))
@@ -70,8 +71,6 @@ describe('MenuResolver', () => {
schemas: [NO_ERRORS_SCHEMA]
});
resolver = TestBed.inject(MenuResolver);
spyOn(menuService, 'addSection');
}));
it('should be created', () => {

View File

@@ -50,7 +50,8 @@ import { AccessControlRoutingModule } from '../../access-control/access-control-
@listableObjectComponent(BrowseEntry, ViewMode.ListElement, DEFAULT_CONTEXT, 'custom')
@Component({
selector: 'ds-browse-entry-list-element',
// eslint-disable-next-line @angular-eslint/component-selector
selector: '',
template: ''
})
class MockThemedBrowseEntryListElementComponent {
@@ -153,19 +154,21 @@ describe('BrowseByComponent', () => {
it('should display a loading message when objects is empty', () => {
(comp as any).objects = undefined;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-themed-loading'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-themed-loading'))).not.toBeNull();
});
it('should display results when objects is not empty', () => {
(comp as any).objects = observableOf({
payload: {
page: {
length: 1
}
}
});
comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: 'authority key 1',
value: 'browse entry 1',
language: null,
count: 1,
}),
]));
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-viewable-collection'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-viewable-collection'))).not.toBeNull();
});
describe('when showPaginator is true and browseEntries are provided', () => {
@@ -237,15 +240,24 @@ describe('BrowseByComponent', () => {
describe('reset filters button', () => {
it('should not be present when no startsWith or value is present ', () => {
const button = fixture.debugElement.query(By.css('reset'));
const button = fixture.debugElement.query(By.css('.reset'));
expect(button).toBeNull();
});
it('should be present when a startsWith or value is present ', () => {
comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: 'authority key 1',
value: 'browse entry 1',
language: null,
count: 1,
}),
]));
comp.shouldDisplayResetButton$ = observableOf(true);
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('reset'));
expect(button).toBeDefined();
const button = fixture.debugElement.query(By.css('.reset'));
expect(button).not.toBeNull();
});
});

View File

@@ -7,7 +7,7 @@
#searchFieldEl>
</div>
<div class="dropdown-divider"></div>
<div class="scrollable-menu"
<ul class="scrollable-menu p-0"
aria-labelledby="dropdownMenuButton"
(scroll)="onScroll($event)"
infiniteScroll
@@ -18,25 +18,25 @@
[scrollWindow]="false"
(scrolled)="onScrollDown()">
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
<li class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
{{'submission.sections.general.no-collection' | translate}}
</button>
</li>
<ng-container *ngIf="searchListCollection?.length > 0">
<button *ngFor="let listItem of searchListCollection"
<li *ngFor="let listItem of searchListCollection"
class="dropdown-item collection-item"
title="{{ listItem.collection.name }}"
(click)="onSelect(listItem)">
<ul class="list-unstyled mb-0">
<li class="list-item text-truncate text-secondary" *ngFor="let item of listItem.communities">
<div class="list-unstyled mb-0">
<div class="list-item text-truncate text-secondary" *ngFor="let item of listItem.communities">
{{ item.name}} <i class="fa fa-level-down" aria-hidden="true"></i>
</div>
<div class="list-item text-truncate text-primary font-weight-bold">{{ listItem.collection.name}}</div>
</div>
</li>
<li class="list-item text-truncate text-primary font-weight-bold">{{ listItem.collection.name}}</li>
</ul>
</button>
</ng-container>
<button class="dropdown-item disabled" *ngIf="(isLoading | async)">
<ds-themed-loading message="{{'loading.default' | translate}}">
</ds-themed-loading>
</button>
</div>
</ul>

View File

@@ -11,6 +11,7 @@ import { PaginatedSearchOptions } from '../../search/models/paginated-search-opt
import { hasValue } from '../../empty.util';
import { createPaginatedList } from '../../testing/utils.test';
import { NotificationsService } from '../../notifications/notifications.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
describe('DSOSelectorComponent', () => {
let component: DSOSelectorComponent;
@@ -34,7 +35,7 @@ describe('DSOSelectorComponent', () => {
];
const searchService = {
search: (options: PaginatedSearchOptions) => {
search: (options: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true) => {
if (hasValue(options.query) && options.query.startsWith('search.resourceid')) {
return createSuccessfulRemoteDataObject$(createPaginatedList([searchResult]));
} else if (options.pagination.currentPage === 1) {
@@ -120,6 +121,43 @@ describe('DSOSelectorComponent', () => {
});
});
describe('search', () => {
beforeEach(() => {
spyOn(searchService, 'search').and.callThrough();
});
it('should specify how to sort if no query is given', () => {
component.sort = new SortOptions('dc.title', SortDirection.ASC);
component.search(undefined, 0);
expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: undefined,
sort: jasmine.objectContaining({
field: 'dc.title',
direction: SortDirection.ASC,
}),
}),
null,
true
);
});
it('should not specify how to sort if a query is given', () => {
component.sort = new SortOptions('dc.title', SortDirection.ASC);
component.search('testQuery', 0);
expect(searchService.search).toHaveBeenCalledWith(
jasmine.objectContaining({
query: 'testQuery',
sort: null,
}),
null,
true
);
});
});
describe('when search returns an error', () => {
beforeEach(() => {
spyOn(searchService, 'search').and.returnValue(createFailedRemoteDataObject$());

View File

@@ -31,6 +31,7 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../empty.util';
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
import { SearchResult } from '../../search/models/search-result.model';
import { SortOptions } from '../../../core/cache/models/sort-options.model';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsService } from '../../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
@@ -69,6 +70,11 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
*/
@Input() types: DSpaceObjectType[];
/**
* The sorting options
*/
@Input() sort: SortOptions;
// list of allowed selectable dsoTypes
typesString: string;
@@ -221,13 +227,16 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
* @param useCache Whether or not to use the cache
*/
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
// default sort is only used when there is not query
let efectiveSort = query ? null : this.sort;
return this.searchService.search(
new PaginatedSearchOptions({
query: query,
dsoTypes: this.types,
pagination: Object.assign({}, this.defaultPagination, {
currentPage: page
})
}),
sort: efectiveSort
}),
null,
useCache,

View File

@@ -8,7 +8,8 @@ import {
getCollectionCreateRoute,
COLLECTION_PARENT_PARAMETER
} from '../../../../collection-page/collection-page-routing-paths';
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
import { environment } from '../../../../../environments/environment';
/**
* Component to wrap a list of existing communities inside a modal
* Used to choose a community from to create a new collection in
@@ -23,6 +24,7 @@ export class CreateCollectionParentSelectorComponent extends DSOSelectorModalWra
selectorTypes = [DSpaceObjectType.COMMUNITY];
action = SelectorActionType.CREATE;
header = 'dso-selector.create.collection.sub-level';
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
super(activeModal, route);

View File

@@ -14,6 +14,6 @@
</h3>
<h5 class="px-2">{{'dso-selector.create.community.sub-level' | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -12,6 +12,8 @@ import {
getCommunityCreateRoute,
COMMUNITY_PARENT_PARAMETER
} from '../../../../community-page/community-page-routing-paths';
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
import { environment } from '../../../../../environments/environment';
/**
* Component to wrap a button - for top communities -
@@ -29,6 +31,7 @@ export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrap
objectType = DSpaceObjectType.COMMUNITY;
selectorTypes = [DSpaceObjectType.COMMUNITY];
action = SelectorActionType.CREATE;
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
super(activeModal, route);

View File

@@ -4,6 +4,8 @@ import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.mod
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component';
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
import { environment } from '../../../../../environments/environment';
/**
* Component to wrap a list of existing collections inside a modal
@@ -21,6 +23,7 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
selectorTypes = [DSpaceObjectType.COLLECTION];
action = SelectorActionType.CREATE;
header = 'dso-selector.create.item.sub-level';
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
/**
* If present this value is used to filter collection list by entity type

View File

@@ -6,6 +6,6 @@
</div>
<div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import { RemoteData } from '../../../core/data/remote-data';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { hasValue, isNotEmpty } from '../../empty.util';
import { SortOptions } from '../../../core/cache/models/sort-options.model';
export enum SelectorActionType {
CREATE = 'create',
@@ -49,6 +50,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit {
*/
action: SelectorActionType;
/**
* Default DSO ordering
*/
defaultSort: SortOptions;
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) {
}

View File

@@ -8,6 +8,8 @@ import {
SelectorActionType
} from '../dso-selector-modal-wrapper.component';
import { getCollectionEditRoute } from '../../../../collection-page/collection-page-routing-paths';
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
import { environment } from '../../../../../environments/environment';
/**
* Component to wrap a list of existing collections inside a modal
@@ -22,6 +24,7 @@ export class EditCollectionSelectorComponent extends DSOSelectorModalWrapperComp
objectType = DSpaceObjectType.COLLECTION;
selectorTypes = [DSpaceObjectType.COLLECTION];
action = SelectorActionType.EDIT;
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
super(activeModal, route);

View File

@@ -8,6 +8,8 @@ import {
SelectorActionType
} from '../dso-selector-modal-wrapper.component';
import { getCommunityEditRoute } from '../../../../community-page/community-page-routing-paths';
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
import { environment } from '../../../../../environments/environment';
/**
* Component to wrap a list of existing communities inside a modal
@@ -23,6 +25,7 @@ export class EditCommunitySelectorComponent extends DSOSelectorModalWrapperCompo
objectType = DSpaceObjectType.COMMUNITY;
selectorTypes = [DSpaceObjectType.COMMUNITY];
action = SelectorActionType.EDIT;
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
super(activeModal, route);

View File

@@ -0,0 +1,11 @@
<div>
<div class="modal-header">{{'dso-selector.'+ action + '.' + objectType.toString().toLowerCase() + '.head' | translate}}
<button type="button" class="close" (click)="close()" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -14,7 +14,7 @@ import { Item } from '../../../../core/shared/item.model';
@Component({
selector: 'ds-edit-item-selector',
templateUrl: '../dso-selector-modal-wrapper.component.html',
templateUrl: 'edit-item-selector.component.html',
})
export class EditItemSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
objectType = DSpaceObjectType.ITEM;

View File

@@ -1,5 +1,5 @@
<a [routerLink]="(bitstreamPath$| async)?.routerLink" class="dont-break-out" [queryParams]="(bitstreamPath$| async)?.queryParams" [target]="isBlank ? '_blank': '_self'" [ngClass]="cssClasses">
<span *ngIf="!(canDownload$ |async)"><i class="fas fa-lock"></i></span>
<span *ngIf="!(canDownload$ |async)" class="pr-1"><i class="fas fa-lock"></i></span>
<ng-container *ngTemplateOutlet="content"></ng-container>
</a>

View File

@@ -12,12 +12,11 @@
[formGroupName]="idx"
[ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]"
cdkDrag
cdkDragHandle
[cdkDragDisabled]="dragDisabled"
[cdkDragPreviewClass]="'ds-submission-reorder-dragging'"
[class.grey-background]="model.isInlineGroupArray">
<!-- Item content -->
<div class="drag-handle" [class.drag-disable]="dragDisabled" tabindex="0">
<div class="drag-handle" [class.drag-disable]="dragDisabled" tabindex="0" cdkDragHandle>
<i class="drag-icon fas fa-grip-vertical fa-fw" [class.drag-disable]="dragDisabled" ></i>
</div>
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: groupModel"></ng-container>

View File

@@ -1,5 +1,5 @@
<div [className]="'float-left w-100 ' + wrapperClass">
<ul class="nav nav-pills d-flex flex-column flex-sm-row" [sortablejs]="chips.getChips()" [sortablejsOptions]="options">
<div role="list" class="nav nav-pills d-flex flex-column flex-sm-row" [sortablejs]="chips.getChips()" [sortablejsOptions]="options">
<ng-container *ngFor="let c of chips.getChips(); let i = index">
<ng-template #tipContent>
<p class="text-left p-0 m-0" *ngFor="let tip of tipText">
@@ -7,7 +7,7 @@
</p>
</ng-template>
<li class="nav-item mr-2 mb-1"
<div role="listitem" class="nav-item mr-2 mb-1"
#t="ngbTooltip"
triggers="manual"
[ngbTooltip]="tipContent"
@@ -40,10 +40,10 @@
<p class="chip-label text-truncate d-table-cell">{{c.display}}</p><i class="fas fa-times ml-2" (click)="removeChips($event, i)" [title]="'chips.remove' | translate"></i>
</span>
</a>
</li>
</div>
</ng-container>
<div [class.chips-sort-ignore]="(isDragging | async)" class="flex-grow-1">
<ng-content ></ng-content>
</div>
</ul>
</div>
</div>

View File

@@ -122,7 +122,7 @@ describe('ChipsComponent test suite', () => {
}));
it('should save chips item index when drag and drop start', fakeAsync(() => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const de = chipsFixture.debugElement.query(By.css('div.nav-item'));
de.triggerEventHandler('dragstart', null);
@@ -131,7 +131,7 @@ describe('ChipsComponent test suite', () => {
it('should update chips item order when drag and drop end', fakeAsync(() => {
spyOn(chipsComp.chips, 'updateOrder');
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const de = chipsFixture.debugElement.query(By.css('div.nav-item'));
de.triggerEventHandler('dragend', null);
@@ -158,7 +158,7 @@ describe('ChipsComponent test suite', () => {
});
it('should show icon for every field that has a configured icon', () => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const de = chipsFixture.debugElement.query(By.css('div.nav-item'));
const icons = de.queryAll(By.css('i.fas'));
expect(icons.length).toBe(4);
@@ -166,7 +166,7 @@ describe('ChipsComponent test suite', () => {
});
it('should show tooltip on mouse over an icon', () => {
const de = chipsFixture.debugElement.query(By.css('li.nav-item'));
const de = chipsFixture.debugElement.query(By.css('div.nav-item'));
const icons = de.queryAll(By.css('i.fas'));
icons[0].triggerEventHandler('mouseover', null);

View File

@@ -109,7 +109,7 @@ describe('LangSwitchComponent', () => {
}));
it('should define the main A HREF in the UI', (() => {
expect(langSwitchElement.querySelector('a')).toBeDefined();
expect(langSwitchElement.querySelector('a')).not.toBeNull();
}));
describe('when selecting a language', () => {

View File

@@ -1,5 +1,5 @@
<ds-themed-loading *ngIf="(loading | async) || (isAuthenticated | async)" class="m-5"></ds-themed-loading>
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="px-4 py-3 login-container">
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="px-4 py-3 mx-auto login-container">
<ng-container *ngFor="let authMethod of (authMethods); let i = index">
<div *ngIf="i === 1" class="text-center mt-2">
<span class="align-middle">{{"login.form.or-divider" | translate}}</span>

View File

@@ -1,3 +1,8 @@
.login-container {
max-width: 450px;
}
a {
white-space: normal;
padding: .25rem .75rem;
}

View File

@@ -72,7 +72,7 @@ describe('ClaimedTaskActionsApproveComponent', () => {
it('should display approve button', () => {
const btn = fixture.debugElement.query(By.css('.btn-success'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display spin icon when approve is pending', () => {
@@ -81,7 +81,7 @@ describe('ClaimedTaskActionsApproveComponent', () => {
const span = fixture.debugElement.query(By.css('.btn-success .fa-spin'));
expect(span).toBeDefined();
expect(span).not.toBeNull();
});
describe('submitTask', () => {

View File

@@ -66,7 +66,7 @@ describe('ClaimedTaskActionsEditMetadataComponent', () => {
it('should display edit button', () => {
const btn = fixture.debugElement.query(By.css('.btn-primary'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
});

View File

@@ -89,7 +89,7 @@ describe('ClaimedTaskActionsRejectComponent', () => {
it('should display reject button', () => {
const btn = fixture.debugElement.query(By.css('.btn-danger'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display spin icon when reject is pending', () => {
@@ -98,7 +98,7 @@ describe('ClaimedTaskActionsRejectComponent', () => {
const span = fixture.debugElement.query(By.css('.btn-danger .fa-spin'));
expect(span).toBeDefined();
expect(span).not.toBeNull();
});
it('should call openRejectModal on reject button click', () => {

View File

@@ -72,7 +72,7 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => {
it('should display return to pool button', () => {
const btn = fixture.debugElement.query(By.css('.btn-secondary'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display spin icon when return to pool action is pending', () => {
@@ -81,7 +81,7 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => {
const span = fixture.debugElement.query(By.css('.btn-secondary .fa-spin'));
expect(span).toBeDefined();
expect(span).not.toBeNull();
});
describe('actionExecution', () => {

View File

@@ -131,13 +131,13 @@ describe('PoolTaskActionsComponent', () => {
it('should display claim task button', () => {
const btn = fixture.debugElement.query(By.css('.btn-info'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
const btn = fixture.debugElement.query(By.css('button[data-test="view-btn"]'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should call claim task with href of getPoolTaskEndpointById', ((done) => {

View File

@@ -107,9 +107,9 @@ describe('WorkflowitemActionsComponent', () => {
});
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
const btn = fixture.debugElement.query(By.css('button[data-test="view-btn"]'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
});

View File

@@ -2,18 +2,21 @@
<button class="btn btn-primary workflow-view mt-1 mb-3" data-test="view-btn"
ngbTooltip="{{'submission.workspace.generic.view-help' | translate}}"
[attr.aria-label]="'submission.workspace.generic.view-help' | translate"
[routerLink]="[getWorkspaceItemViewRoute(object)]">
<i class="fa fa-info-circle"></i> {{"submission.workspace.generic.view" | translate}}
</button>
<a class="btn btn-primary mt-1 mb-3" id="{{'edit_' + object.id}}"
ngbTooltip="{{'submission.workflow.generic.edit-help' | translate}}"
[attr.aria-label]="'submission.workflow.generic.edit-help' | translate"
[routerLink]="['/workspaceitems/' + object.id + '/edit']" role="button">
<i class="fa fa-edit"></i> {{'submission.workflow.generic.edit' | translate}}
</a>
<button type="button" id="{{'delete_' + object.id}}" class="btn btn-danger mt-1 mb-3"
ngbTooltip="{{'submission.workflow.generic.delete-help' | translate}}"
[attr.aria-label]="'submission.workflow.generic.delete-help' | translate"
(click)="$event.preventDefault();confirmDiscard(content)">
<span *ngIf="(processingDelete$ | async)"><i class='fas fa-circle-notch fa-spin'></i>
{{'submission.workflow.tasks.generic.processing' | translate}}</span>

View File

@@ -123,19 +123,19 @@ describe('WorkspaceitemActionsComponent', () => {
it('should display edit button', () => {
const btn = fixture.debugElement.query(By.css('.btn-primary'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display delete button', () => {
const btn = fixture.debugElement.query(By.css('.btn-danger'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
const btn = fixture.debugElement.query(By.css('button[data-test="view-btn"]'));
expect(btn).toBeDefined();
expect(btn).not.toBeNull();
});
describe('on discard confirmation', () => {

View File

@@ -30,27 +30,34 @@ describe('ObjectCollectionComponent', () => {
}).compileComponents(); // compile template and css
}));
beforeEach(waitForAsync(() => {
beforeEach(() => {
fixture = TestBed.createComponent(ObjectCollectionComponent);
objectCollectionComponent = fixture.componentInstance;
}));
fixture.detectChanges();
});
it('should only show the grid component when the viewmode is set to grid', () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.GridElement);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeNull();
expect(fixture.debugElement.query(By.css('ds-object-grid'))).not.toBeNull();
expect(fixture.debugElement.query(By.css('ds-themed-object-list'))).toBeNull();
});
it('should only show the list component when the viewmode is set to list', () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.ListElement);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-themed-object-list'))).not.toBeNull();
expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull();
});
it('should set fallback placeholder font size during test', () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.ListElement);
expect(fixture.debugElement.query(By.css('thumb-font-3'))).toBeDefined();
it('should set fallback placeholder font size during test', async () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.ListElement);
fixture.detectChanges();
const comp = fixture.debugElement.query(By.css('ds-themed-object-list'));
expect(comp).not.toBeNull();
expect(comp.nativeElement.classList).not.toContain('hide-placeholder-text');
});
});

View File

@@ -48,7 +48,7 @@ describe('MyDSpaceItemStatusComponent', () => {
it('should display badge', () => {
const badge = fixture.debugElement.query(By.css('span'));
expect(badge).toBeDefined();
expect(badge).not.toBeNull();
});
it('should init badge content and class', () => {

View File

@@ -63,7 +63,7 @@ describe('ItemSubmitterComponent', () => {
it('should show a badge with submitter name', () => {
const badge = fixture.debugElement.query(By.css('.badge'));
expect(badge).toBeDefined();
expect(badge).not.toBeNull();
expect(badge.nativeElement.innerHTML).toBe(EPersonMock.name);
});
});

View File

@@ -25,7 +25,8 @@
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<ng-container *ngIf="dso.firstMetadataValue('dc.publisher') || dso.firstMetadataValue('dc.date.issued')">
(<span *ngIf="dso.firstMetadataValue('dc.publisher')" class="item-list-publisher" [innerHTML]="firstMetadataValue('dc.publisher') + ', '"></span>
(<span *ngIf="dso.firstMetadataValue('dc.publisher')" class="item-list-publisher" [innerHTML]="firstMetadataValue('dc.publisher')"></span>
<span *ngIf="dso.firstMetadataValue('dc.publisher') && dso.firstMetadataValue('dc.date.issued')">, </span>
<span *ngIf="dso.firstMetadataValue('dc.date.issued')" class="item-list-date" [innerHTML]="firstMetadataValue('dc.date.issued')"></span>)
</ng-container>
<span *ngIf="dso.allMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" class="item-list-authors">

View File

@@ -72,9 +72,9 @@ describe('PageSizeSelectorComponent', () => {
});
it('it should show the size settings with the respective selectable options', (done) => {
(comp as any).paginationOptions$.pipe(first()).subscribe((options) => {
comp.paginationOptions$.pipe(first()).subscribe((options: PaginationComponentOptions) => {
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
expect(pageSizeSetting).toBeDefined();
expect(pageSizeSetting).not.toBeNull();
const childElements = pageSizeSetting.queryAll(By.css('option'));
expect(childElements.length).toEqual(options.pageSizeOptions.length);
done();
@@ -83,10 +83,11 @@ describe('PageSizeSelectorComponent', () => {
});
it('should have the proper rpp value selected by default', (done) => {
(comp as any).paginationOptions$.pipe(take(1)).subscribe((options) => {
comp.paginationOptions$.pipe(take(1)).subscribe(() => {
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
const childElementToBeSelected = pageSizeSetting.query(By.css('option[value="10"][selected="selected"]'));
expect(childElementToBeSelected).toBeDefined();
const childElementToBeSelected = pageSizeSetting.query(By.css('option[value="10"]'));
expect(childElementToBeSelected).not.toBeNull();
expect(childElementToBeSelected.nativeElement.selected).toBeTrue();
done();
});
});

View File

@@ -14,6 +14,6 @@
</h3>
<h5 class="px-2">{{'dso-selector.' + action + '.' + objectType.toString().toLowerCase() + '.input-header' | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -4,6 +4,8 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso-selector/modal-wrappers/dso-selector-modal-wrapper.component';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { environment } from '../../../../environments/environment';
/**
* Component to wrap a button - to select the entire repository -
@@ -33,6 +35,11 @@ export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponen
*/
scopeChange = new EventEmitter<DSpaceObject>();
/**
* Default DSO ordering
*/
defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection);
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) {
super(activeModal, route);
}

View File

@@ -7,6 +7,7 @@ import { SearchService } from '../../../../core/shared/search/search.service';
import { currentPath } from '../../../utils/route.utils';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
import { stripOperatorFromFilterValue } from '../../search.utils';
@Component({
selector: 'ds-search-label',
@@ -83,7 +84,8 @@ export class SearchLabelComponent implements OnInit {
normalizeFilterValue(value: string) {
// const pattern = /,[^,]*$/g;
const pattern = /,authority*$/g;
return value.replace(pattern, '');
value = value.replace(pattern, '');
return stripOperatorFromFilterValue(value);
}
private getFilterName(): string {

View File

@@ -4,7 +4,6 @@ import { Observable } from 'rxjs';
import { Params, Router } from '@angular/router';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { map } from 'rxjs/operators';
import { stripOperatorFromFilterValue } from '../search.utils';
@Component({
selector: 'ds-search-labels',
@@ -37,7 +36,7 @@ export class SearchLabelsComponent {
const labels = {};
Object.keys(params)
.forEach((key) => {
labels[key] = [...params[key].map((value) => stripOperatorFromFilterValue(value))];
labels[key] = [...params[key].map((value) => value)];
});
return labels;
})

View File

@@ -107,6 +107,7 @@ describe('SearchSettingsComponent', () => {
new SortOptions('dc.title', SortDirection.ASC),
new SortOptions('dc.title', SortDirection.DESC)
];
comp.currentSortOption = new SortOptions('score', SortDirection.DESC);
// SearchPageComponent test instance
fixture.detectChanges();
@@ -133,7 +134,8 @@ describe('SearchSettingsComponent', () => {
it('should have the proper order value selected by default', () => {
fixture.detectChanges();
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
const childElementToBeSelected = orderSetting.query(By.css('option[value="score,DESC"][selected="selected"]'));
expect(childElementToBeSelected).toBeDefined();
const childElementToBeSelected = orderSetting.query(By.css('option[value="score,DESC"]'));
expect(childElementToBeSelected).not.toBeNull();
expect(childElementToBeSelected.nativeElement.selected).toBeTrue();
});
});

View File

@@ -78,7 +78,7 @@ describe('SearchSwitchConfigurationComponent', () => {
it('should display select field properly', () => {
const selectField = fixture.debugElement.query(By.css('.form-control'));
expect(selectField).toBeDefined();
expect(selectField).not.toBeNull();
const childElements = selectField.children;
expect(childElements.length).toEqual(comp.configurationList.length);

View File

@@ -188,7 +188,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
* Reset search form control on dropdown menu close
*/
onClose() {
this.collectionDropdown.reset();
this.collectionDropdown?.reset();
}
/**
@@ -199,7 +199,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
*/
toggled(isOpen: boolean) {
if (!isOpen) {
this.collectionDropdown.reset();
this.collectionDropdown?.reset();
}
}
}

View File

@@ -96,6 +96,7 @@ describe('SubmissionFormSectionAddComponent Component', () => {
afterEach(() => {
testFixture.destroy();
testFixture.debugElement.nativeElement.remove();
});
it('should create SubmissionFormSectionAddComponent', inject([SubmissionFormSectionAddComponent], (app: SubmissionFormSectionAddComponent) => {
@@ -163,7 +164,7 @@ describe('SubmissionFormSectionAddComponent Component', () => {
it('should have dropdown menu closed', () => {
expect(dropdowBtn).not.toBeUndefined();
expect(dropdowBtn).not.toBeNull();
expect(dropdownMenu.nativeElement.classList).not.toContain('show');
});

View File

@@ -137,7 +137,7 @@ describe('SubmissionSectionContainerComponent test suite', () => {
const section = fixture.debugElement.query(By.css('[id^=\'sectionContent_\']'));
expect(comp.getSectionContent).toHaveBeenCalled();
expect(section).toBeDefined();
expect(section).not.toBeNull();
});
it('should call removeSection properly', () => {
@@ -165,7 +165,7 @@ describe('SubmissionSectionContainerComponent test suite', () => {
fixture.detectChanges();
sectionErrorsDiv = fixture.debugElement.query(By.css('[id^=\'sectionGenericError_\']'));
expect(sectionErrorsDiv).toBeDefined();
expect(sectionErrorsDiv).not.toBeNull();
});
it('should display warning icon', () => {
@@ -180,7 +180,7 @@ describe('SubmissionSectionContainerComponent test suite', () => {
const iconWarn = fixture.debugElement.query(By.css('i.text-warning'));
const iconErr = fixture.debugElement.query(By.css('i.text-danger'));
const iconSuccess = fixture.debugElement.query(By.css('i.text-success'));
expect(iconWarn).toBeDefined();
expect(iconWarn).not.toBeNull();
expect(iconErr).toBeNull();
expect(iconSuccess).toBeNull();
});
@@ -198,7 +198,7 @@ describe('SubmissionSectionContainerComponent test suite', () => {
const iconErr = fixture.debugElement.query(By.css('i.text-danger'));
const iconSuccess = fixture.debugElement.query(By.css('i.text-success'));
expect(iconWarn).toBeNull();
expect(iconErr).toBeDefined();
expect(iconErr).not.toBeNull();
expect(iconSuccess).toBeNull();
});
@@ -216,7 +216,7 @@ describe('SubmissionSectionContainerComponent test suite', () => {
const iconSuccess = fixture.debugElement.query(By.css('i.text-success'));
expect(iconWarn).toBeNull();
expect(iconErr).toBeNull();
expect(iconSuccess).toBeDefined();
expect(iconSuccess).not.toBeNull();
});
});
@@ -224,7 +224,8 @@ describe('SubmissionSectionContainerComponent test suite', () => {
// declare a test component
@Component({
selector: 'ds-test-cmp',
// eslint-disable-next-line @angular-eslint/component-selector
selector: '',
template: ``
})
class TestComponent {

View File

@@ -4694,7 +4694,7 @@
"submission.workflow.generic.delete": "Delete",
"submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.",
"submission.workflow.generic.delete-help": "Select this option to discard this item. You will then be asked to confirm it.",
"submission.workflow.generic.edit": "Edit",

View File

@@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface';
import { HomeConfig } from './homepage-config.interface';
import { MarkdownConfig } from './markdown-config.interface';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
import { DiscoverySortConfig } from './discovery-sort.config';
interface AppConfig extends Config {
ui: UIServerConfig;
@@ -46,6 +47,7 @@ interface AppConfig extends Config {
info: InfoConfig;
markdown: MarkdownConfig;
vocabularies: FilterVocabularyConfig[];
comcolSelectionSort: DiscoverySortConfig;
}
/**

View File

@@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface';
import { HomeConfig } from './homepage-config.interface';
import { MarkdownConfig } from './markdown-config.interface';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
import { DiscoverySortConfig } from './discovery-sort.config';
export class DefaultAppConfig implements AppConfig {
production = false;
@@ -421,4 +422,10 @@ export class DefaultAppConfig implements AppConfig {
enabled: false
}
];
// Configuration that determines the metadata sorting of community and collection edition and creation when there are not a search query.
comcolSelectionSort: DiscoverySortConfig = {
sortField:'dc.title',
sortDirection:'ASC',
};
}

View File

@@ -0,0 +1,14 @@
import { Config } from './config.interface';
/**
* Config that determines a metadata sorting config.
* It's created mainly to sort by metadata community and collection edition and creation
*/
export class DiscoverySortConfig implements Config {
public sortField: string;
/**
* ASC / DESC values expected
*/
public sortDirection: string;
}

View File

@@ -297,6 +297,10 @@ export const environment: BuildConfig = {
enabled: false,
mathjax: false,
},
comcolSelectionSort: {
sortField:'dc.title',
sortDirection:'ASC',
},
vocabularies: [
{

View File

@@ -246,3 +246,8 @@ ul.dso-edit-menu-dropdown > li .nav-item.nav-link {
padding: 0;
display: inline;
}
.table th,
.table td {
vertical-align: middle;
}

View File

@@ -6,6 +6,6 @@
</div>
<div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -14,6 +14,6 @@
</h3>
<h5 class="px-2">{{'dso-selector.create.community.sub-level' | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -6,6 +6,6 @@
</div>
<div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>

View File

@@ -6,6 +6,6 @@
</div>
<div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div>
</div>