Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit Item, Bitstreams tab: Accessibility improvements #3464

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cf54af2
117803: Refactor Item Edit Bitstreams page to use HTML Table elements
AAwouters Sep 3, 2024
a11bfc8
117803: Hide table headers for subsequent bundle tables
AAwouters Sep 3, 2024
1773a75
117803: Allow changing page size of Bitstream list
AAwouters Sep 4, 2024
d85124c
117803: Fix deleted bitstreams not being removed from list
AAwouters Sep 4, 2024
374a9ae
117803: Add item-bitstream service
AAwouters Sep 4, 2024
3f4bf7c
117803: Fix existing tests
AAwouters Sep 4, 2024
d8b426d
117803: Add ItemBitsreamsService tests
AAwouters Sep 5, 2024
cc5b841
117803: Only visually hide header rows
AAwouters Sep 10, 2024
8481604
117803: Fix table & pagination margin
AAwouters Sep 10, 2024
79f3a31
117803: Set header border to background color
AAwouters Sep 10, 2024
6a8095d
117803: Change pagination settings styling
AAwouters Sep 10, 2024
a230eee
117803: Add negative top-margin to subsequent tables
AAwouters Sep 10, 2024
be99cc5
118219: Allow dragging of table rows
AAwouters Sep 11, 2024
eadbcdb
118219: Store result of drag & dropping bitstream
AAwouters Sep 11, 2024
6a2c7d0
118219: Add dragging tooltip explaining how to drag to other page
AAwouters Sep 12, 2024
8a16597
118219: Fix tests
AAwouters Sep 12, 2024
a207fb5
118219: Remove unused paginated-drag-and-drop components
AAwouters Sep 12, 2024
d674bcc
118220: Integrate live-region component into item-edit-bitsream page
AAwouters Sep 16, 2024
1f909dc
118223: Add instructive alert to item-bitstreams edit page
AAwouters Sep 17, 2024
181ea6d
118223: Implement bitstream reordering with keyboard
AAwouters Sep 18, 2024
2e1b148
118223: Stop space from scrolling down page
AAwouters Oct 4, 2024
0920a21
118223: Stop sending success notificiations on every move
AAwouters Oct 4, 2024
b158c5c
118223: Move drag tooltip to center of pagination numbers
AAwouters Oct 4, 2024
1dcc5d1
118223: Add ItemBitstreams service tests
AAwouters Oct 4, 2024
0bdb574
118223: Remove unused item-edit-bitstream component
AAwouters Oct 4, 2024
e8379db
118223: Add item-bitstreams component tests
AAwouters Oct 7, 2024
7fb4755
118223: Add item-edit-bitstream-bundle component tests
AAwouters Oct 8, 2024
2b1b9d8
118223: Include selection action with selection
AAwouters Oct 9, 2024
8d93f22
119176: Make table horizontally scrollable
AAwouters Oct 9, 2024
5bb6f6d
119176: Announce notification content in live region
AAwouters Oct 14, 2024
93f9341
119176: Add aria-labels to buttons
AAwouters Oct 16, 2024
6644714
119176: clear viewContainerRef on destroy
AAwouters Oct 16, 2024
6181adc
Merge branch 'item-edit-bitstreams-table-7.6' into item-edit-bitstrea…
AAwouters Oct 17, 2024
1a81622
118223: Add 'loading' overlay while bitstream is moving
AAwouters Oct 23, 2024
5a88ced
118223: Remove console.log
AAwouters Dec 11, 2024
cc70eaa
Merge branch 'item-edit-bitstreams-table-7.6' into item-edit-bitstrea…
AAwouters Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');

}
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-loading *ngIf="isProcessingMoveRequest | async" class="loading-overlay"></ds-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 @@ -7,6 +7,7 @@ import {
TestBed,
waitForAsync,
} from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
ActivatedRoute,
Router,
Expand All @@ -15,7 +16,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';

import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { RestResponse } from '../../../core/cache/response.models';
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
import { BundleDataService } from '../../../core/data/bundle-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';
Expand Down Expand Up @@ -44,8 +44,12 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
import { VarDirective } from '../../../shared/utils/var.directive';
import { ItemBitstreamsComponent } from './item-bitstreams.component';
import { ItemBitstreamsService } from './item-bitstreams.service';
import {
getItemBitstreamsServiceStub,
ItemBitstreamsServiceStub,
} from './item-bitstreams.service.stub';
import { ItemEditBitstreamBundleComponent } from './item-edit-bitstream-bundle/item-edit-bitstream-bundle.component';
import { ItemEditBitstreamDragHandleComponent } from './item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component';

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

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

itemBitstreamsService = getItemBitstreamsServiceStub();

TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), ItemBitstreamsComponent, ObjectValuesPipe, VarDirective],
imports: [
TranslateModule.forRoot(),
ItemBitstreamsComponent,
ObjectValuesPipe,
VarDirective,
BrowserAnimationsModule,
],
providers: [
{ provide: ItemDataService, useValue: itemService },
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
Expand All @@ -181,6 +194,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 @@ -189,7 +203,6 @@ describe('ItemBitstreamsComponent', () => {
.overrideComponent(ItemBitstreamsComponent, {
remove: {
imports: [ItemEditBitstreamBundleComponent,
ItemEditBitstreamDragHandleComponent,
ThemedLoadingComponent],
},
})
Expand All @@ -209,28 +222,8 @@ describe('ItemBitstreamsComponent', () => {
comp.submit();
});

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

it('should not call removeMultiple on the bitstreamService for the unmarked field', () => {
expect(bitstreamService.removeMultiple).not.toHaveBeenCalledWith([bitstream1]);
});
});

describe('when dropBitstream is called', () => {
beforeEach((done) => {
comp.dropBitstream(bundle, {
fromIndex: 0,
toIndex: 50,
finish: () => {
done();
},
});
});

it('should send out a patch for the move operation', () => {
expect(bundleService.patch).toHaveBeenCalled();
it('should call removeMarkedBitstreams on the itemBitstreamsService', () => {
expect(itemBitstreamsService.removeMarkedBitstreams).toHaveBeenCalled();
});
});

Expand All @@ -247,4 +240,114 @@ describe('ItemBitstreamsComponent', () => {
expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(bundle.self);
});
});

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('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 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('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('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
Loading