Skip to content

Commit

Permalink
Merge pull request #3466 from atmire/item-edit-bitstreams-table-7_x
Browse files Browse the repository at this point in the history
[Port dspace-7_x] Edit Item, Bitstreams tab: Accessibility improvements
  • Loading branch information
tdonohue authored Dec 18, 2024
2 parents 6555585 + bee8bde commit 36dd476
Show file tree
Hide file tree
Showing 28 changed files with 2,667 additions and 1,214 deletions.
28 changes: 28 additions & 0 deletions src/app/core/data/object-updates/object-updates.service.stub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export class ObjectUpdatesServiceStub {

initialize = jasmine.createSpy('initialize');
saveFieldUpdate = jasmine.createSpy('saveFieldUpdate');
getObjectEntry = jasmine.createSpy('getObjectEntry');
getFieldState = jasmine.createSpy('getFieldState');
getFieldUpdates = jasmine.createSpy('getFieldUpdates');
getFieldUpdatesExclusive = jasmine.createSpy('getFieldUpdatesExclusive');
isValid = jasmine.createSpy('isValid');
isValidPage = jasmine.createSpy('isValidPage');
saveAddFieldUpdate = jasmine.createSpy('saveAddFieldUpdate');
saveRemoveFieldUpdate = jasmine.createSpy('saveRemoveFieldUpdate');
saveChangeFieldUpdate = jasmine.createSpy('saveChangeFieldUpdate');
isSelectedVirtualMetadata = jasmine.createSpy('isSelectedVirtualMetadata');
setSelectedVirtualMetadata = jasmine.createSpy('setSelectedVirtualMetadata');
setEditableFieldUpdate = jasmine.createSpy('setEditableFieldUpdate');
setValidFieldUpdate = jasmine.createSpy('setValidFieldUpdate');
discardFieldUpdates = jasmine.createSpy('discardFieldUpdates');
discardAllFieldUpdates = jasmine.createSpy('discardAllFieldUpdates');
reinstateFieldUpdates = jasmine.createSpy('reinstateFieldUpdates');
removeSingleFieldUpdate = jasmine.createSpy('removeSingleFieldUpdate');
getUpdateFields = jasmine.createSpy('getUpdateFields');
hasUpdates = jasmine.createSpy('hasUpdates');
isReinstatable = jasmine.createSpy('isReinstatable');
getLastModified = jasmine.createSpy('getLastModified');
createPatch = jasmine.createSpy('getPatch');

}
6 changes: 0 additions & 6 deletions src/app/item-page/edit-item-page/edit-item-page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { ItemPrivateComponent } from './item-private/item-private.component';
import { ItemPublicComponent } from './item-public/item-public.component';
import { ItemDeleteComponent } from './item-delete/item-delete.component';
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
import { ItemEditBitstreamComponent } from './item-bitstreams/item-edit-bitstream/item-edit-bitstream.component';
import { SearchPageModule } from '../../search-page/search-page.module';
import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component';
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
Expand All @@ -26,8 +25,6 @@ import { ItemMoveComponent } from './item-move/item-move.component';
import { ItemEditBitstreamBundleComponent } from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component';
import { BundleDataService } from '../../core/data/bundle-data.service';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component';
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
Expand Down Expand Up @@ -83,14 +80,11 @@ import {
ItemRelationshipsComponent,
ItemBitstreamsComponent,
ItemVersionHistoryComponent,
ItemEditBitstreamComponent,
ItemEditBitstreamBundleComponent,
PaginatedDragAndDropBitstreamListComponent,
EditRelationshipComponent,
EditRelationshipListComponent,
ItemCollectionMapperComponent,
ItemMoveComponent,
ItemEditBitstreamDragHandleComponent,
VirtualMetadataComponent,
ItemAuthorizationsComponent,
IdentifierDataComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<div class="item-bitstreams" *ngVar="(bundles$ | async) as bundles">
<div class="mt-2" id="reorder-description">
<ds-alert [content]="'item.edit.bitstreams.info-alert'" [type]="AlertType.Info"></ds-alert>
</div>

<div class="button-row top d-flex mt-2 space-children-mr">
<button class="mr-auto btn btn-success"
[attr.aria-label]="'item.edit.bitstreams.upload-button' | translate"
Expand Down Expand Up @@ -27,21 +31,13 @@
</button>
</div>

<div *ngIf="item && bundles?.length > 0" class="container table-bordered mt-4">
<div class="row header-row font-weight-bold">
<div class="{{columnSizes.columns[0].buildClasses()}} row-element">
<ds-item-edit-bitstream-drag-handle></ds-item-edit-bitstream-drag-handle>
{{'item.edit.bitstreams.headers.name' | translate}}
</div>
<div class="{{columnSizes.columns[1].buildClasses()}} row-element">{{'item.edit.bitstreams.headers.description' | translate}}</div>
<div class="{{columnSizes.columns[2].buildClasses()}} text-center row-element">{{'item.edit.bitstreams.headers.format' | translate}}</div>
<div class="{{columnSizes.columns[3].buildClasses()}} text-center row-element">{{'item.edit.bitstreams.headers.actions' | translate}}</div>
</div>
<ds-item-edit-bitstream-bundle *ngFor="let bundle of bundles"
<div *ngIf="item && bundles?.length > 0" class="mt-4 table-border scrollable-table" [ngClass]="{'disabled-overlay': (isProcessingMoveRequest | async)}">
<ds-item-edit-bitstream-bundle *ngFor="let bundle of bundles; first as isFirst"
[bundle]="bundle"
[item]="item"
[columnSizes]="columnSizes"
(dropObject)="dropBitstream(bundle, $event)">
[isFirstTable]="isFirst"
aria-describedby="reorder-description">
</ds-item-edit-bitstream-bundle>
</div>
<div *ngIf="bundles?.length === 0"
Expand Down Expand Up @@ -74,3 +70,5 @@
</div>
</div>
</div>

<ds-themed-loading *ngIf="isProcessingMoveRequest | async" class="loading-overlay"></ds-themed-loading>
Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
.header-row {
color: var(--bs-table-dark-color);
background-color: var(--bs-table-dark-bg);
border-color: var(--bs-table-dark-border-color);
}

.bundle-row {
color: var(--bs-table-head-color);
background-color: var(--bs-table-head-bg);
border-color: var(--bs-table-border-color);
}

.row-element {
padding: 12px;
padding: 0.75em;
border-bottom: var(--bs-table-border-width) solid var(--bs-table-border-color);
}

.drag-handle {
visibility: hidden;
&:hover {
cursor: move;
}
Expand All @@ -27,10 +8,6 @@
cursor: move;
}

:host ::ng-deep .bitstream-row:hover .drag-handle, :host ::ng-deep .bitstream-row-drag-handle:focus .drag-handle {
visibility: visible !important;
}

.cdk-drag-preview {
margin-left: 0;
box-sizing: border-box;
Expand All @@ -54,3 +31,25 @@
:host ::ng-deep .larger-tooltip .tooltip-inner {
max-width: 500px;
}

.table-border {
border: 1px solid #dee2e6;
}

:host ::ng-deep .pagination {
padding-top: 0.5rem;
}

.scrollable-table {
overflow-x: auto;
}

.disabled-overlay {
opacity: 0.6;
}

.loading-overlay {
position: fixed;
top: 50%;
left: 50%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
import { VarDirective } from '../../../shared/utils/var.directive';
import { BundleDataService } from '../../../core/data/bundle-data.service';
import { Bundle } from '../../../core/shared/bundle.model';
import { RestResponse } from '../../../core/cache/response.models';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { RouterStub } from '../../../shared/testing/router.stub';
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model';
import { BitstreamDataServiceStub } from '../../../shared/testing/bitstream-data-service.stub';
import { ItemBitstreamsService } from './item-bitstreams.service';
import { getItemBitstreamsServiceStub, ItemBitstreamsServiceStub } from './item-bitstreams.service.stub';

let comp: ItemBitstreamsComponent;
let fixture: ComponentFixture<ItemBitstreamsComponent>;
Expand Down Expand Up @@ -77,6 +78,7 @@ let objectCache: ObjectCacheService;
let requestService: RequestService;
let searchConfig: SearchConfigurationService;
let bundleService: BundleDataService;
let itemBitstreamsService: ItemBitstreamsServiceStub;

describe('ItemBitstreamsComponent', () => {
beforeEach(waitForAsync(() => {
Expand Down Expand Up @@ -145,9 +147,11 @@ describe('ItemBitstreamsComponent', () => {
url: url
});
bundleService = jasmine.createSpyObj('bundleService', {
patch: observableOf(new RestResponse(true, 200, 'OK'))
patch: createSuccessfulRemoteDataObject$({}),
});

itemBitstreamsService = getItemBitstreamsServiceStub();

TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ItemBitstreamsComponent, ObjectValuesPipe, VarDirective],
Expand All @@ -162,6 +166,7 @@ describe('ItemBitstreamsComponent', () => {
{ provide: RequestService, useValue: requestService },
{ provide: SearchConfigurationService, useValue: searchConfig },
{ provide: BundleDataService, useValue: bundleService },
{ provide: ItemBitstreamsService, useValue: itemBitstreamsService },
ChangeDetectorRef
], schemas: [
NO_ERRORS_SCHEMA
Expand All @@ -182,56 +187,132 @@ describe('ItemBitstreamsComponent', () => {
comp.submit();
});

it('should call removeMultiple on the bitstreamService for the marked field', () => {
expect(bitstreamService.removeMultiple).toHaveBeenCalledWith([bitstream2]);
it('should call removeMarkedBitstreams on the itemBitstreamsService', () => {
expect(itemBitstreamsService.removeMarkedBitstreams).toHaveBeenCalled();
});
});

it('should not call removeMultiple on the bitstreamService for the unmarked field', () => {
expect(bitstreamService.removeMultiple).not.toHaveBeenCalledWith([bitstream1]);
describe('discard', () => {
it('should discard ALL field updates', () => {
comp.discard();
expect(objectUpdatesService.discardAllFieldUpdates).toHaveBeenCalled();
});
});

describe('when dropBitstream is called', () => {
const event = {
fromIndex: 0,
toIndex: 50,
// eslint-disable-next-line no-empty,@typescript-eslint/no-empty-function
finish: () => {
}
};
describe('reinstate', () => {
it('should reinstate field updates on the bundle', () => {
comp.reinstate();
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(bundle.self);
});
});

beforeEach(() => {
comp.dropBitstream(bundle, event);
describe('moveUp', () => {
it('should move the selected bitstream up', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(true);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.moveUp(event);

expect(itemBitstreamsService.moveSelectedBitstreamUp).toHaveBeenCalled();
});

it('should not do anything if no bitstream is selected', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(false);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.moveUp(event);

expect(itemBitstreamsService.moveSelectedBitstreamUp).not.toHaveBeenCalled();
});
});

describe('when dropBitstream is called', () => {
beforeEach((done) => {
comp.dropBitstream(bundle, {
fromIndex: 0,
toIndex: 50,
finish: () => {
done();
}
});
describe('moveDown', () => {
it('should move the selected bitstream down', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(true);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.moveDown(event);

expect(itemBitstreamsService.moveSelectedBitstreamDown).toHaveBeenCalled();
});

it('should send out a patch for the move operation', () => {
expect(bundleService.patch).toHaveBeenCalled();
it('should not do anything if no bitstream is selected', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(false);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.moveDown(event);

expect(itemBitstreamsService.moveSelectedBitstreamDown).not.toHaveBeenCalled();
});
});

describe('discard', () => {
it('should discard ALL field updates', () => {
comp.discard();
expect(objectUpdatesService.discardAllFieldUpdates).toHaveBeenCalled();
describe('cancelSelection', () => {
it('should cancel the selection', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(true);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.cancelSelection(event);

expect(itemBitstreamsService.cancelSelection).toHaveBeenCalled();
});

it('should not do anything if no bitstream is selected', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(false);

const event = {
preventDefault: () => {/* Intentionally empty */},
} as KeyboardEvent;
comp.cancelSelection(event);

expect(itemBitstreamsService.cancelSelection).not.toHaveBeenCalled();
});
});

describe('reinstate', () => {
it('should reinstate field updates on the bundle', () => {
comp.reinstate();
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(bundle.self);
describe('clearSelection', () => {
it('should clear the selection', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(true);

const event = {
target: document.createElement('BODY'),
preventDefault: () => {/* Intentionally empty */},
} as unknown as KeyboardEvent;
comp.clearSelection(event);

expect(itemBitstreamsService.clearSelection).toHaveBeenCalled();
});

it('should not do anything if no bitstream is selected', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(false);

const event = {
target: document.createElement('BODY'),
preventDefault: () => {/* Intentionally empty */},
} as unknown as KeyboardEvent;
comp.clearSelection(event);

expect(itemBitstreamsService.clearSelection).not.toHaveBeenCalled();
});

it('should not do anything if the event target is not \'BODY\'', () => {
itemBitstreamsService.hasSelectedBitstream.and.returnValue(true);

const event = {
target: document.createElement('NOT-BODY'),
preventDefault: () => {/* Intentionally empty */},
} as unknown as KeyboardEvent;
comp.clearSelection(event);

expect(itemBitstreamsService.clearSelection).not.toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 36dd476

Please sign in to comment.