Skip to content

Commit 363501d

Browse files
author
Ali Jaber
committed
144398: fix bundle removal state and visual regressions
1 parent b33e31f commit 363501d

7 files changed

Lines changed: 111 additions & 45 deletions

src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<div class="item-bitstreams" *ngVar="(bundles$ | async) as bundles">
1+
@let bundles = (bundles$ | async);
2+
<div class="item-bitstreams">
23
<div class="mt-2" id="reorder-description">
34
<ds-alert [content]="'item.edit.bitstreams.info-alert'" [type]="AlertType.Info"></ds-alert>
45
</div>
@@ -37,19 +38,18 @@
3738

3839
@if (item && bundles?.length > 0) {
3940
<div class="mt-4 table-border scrollable-table" [ngClass]="{'disabled-overlay': (isProcessingMoveRequest | async)}">
40-
<ng-container *ngVar="(bundleFieldUpdates$ | async) as bundleUpdates">
41-
@for (bundle of bundles; track bundle; let isFirst = $first) {
42-
<ds-item-edit-bitstream-bundle
43-
[bundle]="bundle"
44-
[item]="item"
45-
[columnSizes]="columnSizes"
46-
[isFirstTable]="isFirst"
47-
[bundleUpdate]="bundleUpdates?.[bundle.uuid]"
48-
[bundleUpdatesUrl]="bundleUpdatesUrl"
49-
aria-describedby="reorder-description">
50-
</ds-item-edit-bitstream-bundle>
51-
}
52-
</ng-container>
41+
@let bundleUpdates = (bundleFieldUpdates$ | async);
42+
@for (bundle of bundles; track bundle.uuid; let isFirst = $first) {
43+
<ds-item-edit-bitstream-bundle
44+
[bundle]="bundle"
45+
[item]="item"
46+
[columnSizes]="columnSizes"
47+
[isFirstTable]="isFirst"
48+
[bundleUpdate]="bundleUpdates?.[bundle.uuid]"
49+
[bundleUpdatesUrl]="bundleUpdatesUrl"
50+
aria-describedby="reorder-description">
51+
</ds-item-edit-bitstream-bundle>
52+
}
5353
</div>
5454
}
5555
@if (bundles?.length === 0) {

src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import {
22
ChangeDetectorRef,
3+
Component,
4+
Input,
35
NO_ERRORS_SCHEMA,
46
} from '@angular/core';
57
import {
68
ComponentFixture,
79
TestBed,
810
waitForAsync,
911
} from '@angular/core/testing';
12+
import { By } from '@angular/platform-browser';
1013
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
1114
import {
1215
ActivatedRoute,
@@ -40,12 +43,14 @@ import {
4043
} from '@dspace/core/utilities/remote-data.utils';
4144
import { hasValue } from '@dspace/shared/utils/empty.util';
4245
import { TranslateModule } from '@ngx-translate/core';
43-
import { of } from 'rxjs';
46+
import {
47+
BehaviorSubject,
48+
of,
49+
} from 'rxjs';
4450

4551
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
4652
import { SearchConfigurationService } from '../../../shared/search/search-configuration.service';
4753
import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
48-
import { VarDirective } from '../../../shared/utils/var.directive';
4954
import { ItemBitstreamsComponent } from './item-bitstreams.component';
5055
import { ItemBitstreamsService } from './item-bitstreams.service';
5156
import {
@@ -105,6 +110,21 @@ let requestService: RequestService;
105110
let searchConfig: SearchConfigurationService;
106111
let bundleService: BundleDataService;
107112
let itemBitstreamsService: ItemBitstreamsServiceStub;
113+
let bundlesRD$: BehaviorSubject<any>;
114+
let bundleFieldUpdates$: BehaviorSubject<any>;
115+
116+
@Component({
117+
selector: 'ds-item-edit-bitstream-bundle',
118+
template: '',
119+
})
120+
class ItemEditBitstreamBundleTestComponent {
121+
@Input() bundle: Bundle;
122+
@Input() item: Item;
123+
@Input() columnSizes;
124+
@Input() isFirstTable: boolean;
125+
@Input() bundleUpdate;
126+
@Input() bundleUpdatesUrl: string;
127+
}
108128

109129
describe('ItemBitstreamsComponent', () => {
110130
beforeEach(waitForAsync(() => {
@@ -131,11 +151,12 @@ describe('ItemBitstreamsComponent', () => {
131151
getMoveOperations: of(moveOperations),
132152
},
133153
);
154+
bundleFieldUpdates$ = new BehaviorSubject({
155+
[bundle.uuid]: { field: bundle, changeType: undefined },
156+
});
134157
(objectUpdatesService.getFieldUpdatesExclusive as jasmine.Spy).and.callFake((bundleListUrl: string) => {
135158
if (hasValue(bundleListUrl) && bundleListUrl.endsWith('/bundles')) {
136-
return of({
137-
[bundle.uuid]: { field: bundle, changeType: undefined },
138-
});
159+
return bundleFieldUpdates$.asObservable();
139160
}
140161
return of({
141162
[bitstream1.uuid]: fieldUpdate1,
@@ -183,10 +204,11 @@ describe('ItemBitstreamsComponent', () => {
183204
data: of({}),
184205
url: url,
185206
});
207+
bundlesRD$ = new BehaviorSubject(createSuccessfulRemoteDataObject(createPaginatedList([bundle])));
186208
bundleService = jasmine.createSpyObj('bundleService', {
187209
patch: createSuccessfulRemoteDataObject$({}),
188210
removeMultiple: createSuccessfulRemoteDataObject$({} as NoContent),
189-
findAllByItem: createSuccessfulRemoteDataObject$(createPaginatedList([bundle])),
211+
findAllByItem: bundlesRD$.asObservable(),
190212
});
191213

192214
itemBitstreamsService = getItemBitstreamsServiceStub();
@@ -196,7 +218,6 @@ describe('ItemBitstreamsComponent', () => {
196218
TranslateModule.forRoot(),
197219
ItemBitstreamsComponent,
198220
ObjectValuesPipe,
199-
VarDirective,
200221
BrowserAnimationsModule,
201222
],
202223
providers: [
@@ -221,6 +242,9 @@ describe('ItemBitstreamsComponent', () => {
221242
imports: [ItemEditBitstreamBundleComponent,
222243
ThemedLoadingComponent],
223244
},
245+
add: {
246+
imports: [ItemEditBitstreamBundleTestComponent],
247+
},
224248
})
225249
.compileComponents();
226250
}));
@@ -232,6 +256,27 @@ describe('ItemBitstreamsComponent', () => {
232256
fixture.detectChanges();
233257
});
234258

259+
it('should preserve bundle components and staged updates when bundle observables emit again', () => {
260+
const bundleDebugElement = fixture.debugElement.query(By.directive(ItemEditBitstreamBundleTestComponent));
261+
const bundleComponent = bundleDebugElement.componentInstance as ItemEditBitstreamBundleTestComponent;
262+
263+
bundleFieldUpdates$.next({
264+
[bundle.uuid]: { field: bundle, changeType: FieldChangeType.REMOVE },
265+
});
266+
fixture.detectChanges();
267+
268+
expect(fixture.debugElement.query(By.directive(ItemEditBitstreamBundleTestComponent)).componentInstance)
269+
.toBe(bundleComponent);
270+
expect(bundleComponent.bundleUpdate.changeType).toBe(FieldChangeType.REMOVE);
271+
272+
bundlesRD$.next(createSuccessfulRemoteDataObject(createPaginatedList([bundle])));
273+
fixture.detectChanges();
274+
275+
expect(fixture.debugElement.query(By.directive(ItemEditBitstreamBundleTestComponent)).componentInstance)
276+
.toBe(bundleComponent);
277+
expect(objectUpdatesService.initialize).toHaveBeenCalledTimes(1);
278+
});
279+
235280
describe('when submit is called', () => {
236281
beforeEach(() => {
237282
spyOn(bitstreamService, 'removeMultiple').and.callThrough();

src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
6161
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
6262
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
6363
import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
64-
import { VarDirective } from '../../../shared/utils/var.directive';
6564
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
6665
import { ItemBitstreamsService } from './item-bitstreams.service';
6766
import { ItemEditBitstreamBundleComponent } from './item-edit-bitstream-bundle/item-edit-bitstream-bundle.component';
@@ -79,7 +78,6 @@ import { ItemEditBitstreamBundleComponent } from './item-edit-bitstream-bundle/i
7978
RouterLink,
8079
ThemedLoadingComponent,
8180
TranslateModule,
82-
VarDirective,
8381
],
8482
providers: [ObjectValuesPipe],
8583
})
@@ -173,7 +171,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
173171
shareReplay({ bufferSize: 1, refCount: true }),
174172
);
175173

176-
this.subs.push(this.bundles$.subscribe((bundles: Bundle[]) => {
174+
this.subs.push(this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => {
177175
this.objectUpdatesService.initialize(this.bundleUpdatesUrl, bundles, new Date());
178176
}));
179177

src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
}
4343
</div>
4444
</th>
45-
<td class="text-end row-element bundle-row-actions {{ columnSizes.columns[3].buildClasses() }}">
46-
<div class="d-flex justify-content-end w-100">
45+
<td class="text-center row-element bundle-row-actions {{ columnSizes.columns[3].buildClasses() }}">
4746
<div class="btn-group">
4847
<button [routerLink]="[itemPageRoute, 'bitstreams', 'new']"
4948
[queryParams]="{bundle: bundle.id}"
@@ -53,12 +52,12 @@
5352
title="{{'item.edit.bitstreams.bundle.edit.buttons.upload' | translate}}">
5453
<i class="fas fa-upload fa-fw"></i>
5554
</button>
56-
<button [dsBtnDisabled]="!canRemoveBundle()" (click)="removeBundle()"
55+
<button type="button" [dsBtnDisabled]="!canRemoveBundle()" (click)="removeBundle()"
5756
class="btn btn-outline-danger btn-sm"
5857
title="{{'item.edit.bitstreams.bundle.edit.buttons.remove' | translate}}">
5958
<i class="fas fa-trash-alt fa-fw"></i>
6059
</button>
61-
<button [dsBtnDisabled]="!canUndoBundleRemove()" (click)="undoBundleRemove()"
60+
<button type="button" [dsBtnDisabled]="!canUndoBundleRemove()" (click)="undoBundleRemove()"
6261
class="btn btn-outline-warning btn-sm"
6362
title="{{'item.edit.bitstreams.bundle.edit.buttons.undo' | translate}}">
6463
<i class="fas fa-undo-alt fa-fw"></i>
@@ -91,7 +90,6 @@
9190
</ul>
9291
</div>
9392
</div>
94-
</div>
9593
</td>
9694
</tr>
9795
@for (entry of (tableEntries$ | async); track entry) {
@@ -101,11 +99,13 @@
10199
(cdkDragStarted)="dragStart()" (cdkDragEnded)="dragEnd()">
102100
<th class="bitstream-name row-element {{ columnSizes.columns[0].buildClasses() }}"
103101
scope="row" id="{{ entry.nameStripped }}" headers="{{ bundleName }} name">
104-
<div class="drag-handle text-muted float-start p-1 me-2 d-inline" tabindex="0" cdkDragHandle
105-
(keydown.enter)="select($event, entry)" (keydown.space)="select($event, entry)" (click)="select($event, entry)">
106-
<i class="drag-icon" [title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
102+
<div class="d-flex align-items-center">
103+
<div class="drag-handle text-muted me-2" tabindex="0" cdkDragHandle
104+
(keydown.enter)="select($event, entry)" (keydown.space)="select($event, entry)" (click)="select($event, entry)">
105+
<i class="drag-icon" [title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
106+
</div>
107+
<span class="dont-break-out">{{ entry.name }}</span>
107108
</div>
108-
<span class="dont-break-out">{{ entry.name }}</span>
109109
</th>
110110
<td class="row-element {{ columnSizes.columns[1].buildClasses() }}"
111111
headers="{{ entry.nameStripped }} {{ bundleName }} description">

src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,20 @@
77
--bs-table-color-state: var(--bs-light);
88
}
99

10-
.bundle-row {
10+
.bundle-row:not(.table-danger) {
1111
--bs-table-color-state: var(--bs-dark);
1212
--bs-table-bg: var(--bs-light-bg-subtle);
1313
--bs-table-bg-state: var(--bs-light-bg-subtle);
14+
}
1415

16+
.bundle-row {
1517
.bundle-row-name.row-element {
1618
padding-right: 1.25rem;
1719

1820
@media (min-width: 768px) {
1921
padding-right: 1.75rem;
2022
}
2123
}
22-
23-
.bundle-row-actions.row-element {
24-
padding-left: 1rem;
25-
26-
@media (min-width: 768px) {
27-
padding-left: 1.25rem;
28-
}
29-
}
3024
}
3125

3226
.row-element {

src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@ import {
88
TestBed,
99
waitForAsync,
1010
} from '@angular/core/testing';
11+
import { By } from '@angular/platform-browser';
12+
import { provideRouter } from '@angular/router';
1113
import { BundleDataService } from '@dspace/core/data/bundle-data.service';
1214
import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service';
1315
import { FieldChangeType } from '@dspace/core/data/object-updates/field-change-type.model';
1416
import { FieldUpdate } from '@dspace/core/data/object-updates/field-update.model';
1517
import { ObjectUpdatesService } from '@dspace/core/data/object-updates/object-updates.service';
1618
import { RequestService } from '@dspace/core/data/request.service';
1719
import { PaginationService } from '@dspace/core/pagination/pagination.service';
20+
import { PaginationComponentOptions } from '@dspace/core/pagination/pagination-component-options.model';
1821
import { Bundle } from '@dspace/core/shared/bundle.model';
1922
import { Item } from '@dspace/core/shared/item.model';
2023
import { AuthorizationDataServiceStub } from '@dspace/core/testing/authorization-service.stub';
2124
import { PaginationServiceStub } from '@dspace/core/testing/pagination-service.stub';
2225
import { getMockRequestService } from '@dspace/core/testing/request.service.mock';
2326
import { createPaginatedList } from '@dspace/core/testing/utils.test';
2427
import { createSuccessfulRemoteDataObject$ } from '@dspace/core/utilities/remote-data.utils';
28+
import { provideMockStore } from '@ngrx/store/testing';
2529
import { TranslateModule } from '@ngx-translate/core';
2630
import {
2731
of,
@@ -77,12 +81,20 @@ describe('ItemEditBitstreamBundleComponent', () => {
7781
beforeEach(waitForAsync(() => {
7882
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', {
7983
initialize: undefined,
80-
getFieldUpdatesExclusive: of(null),
84+
getFieldUpdatesExclusive: of({}),
8185
saveRemoveFieldUpdate: undefined,
8286
removeSingleFieldUpdate: undefined,
8387
});
8488

8589
itemBitstreamsService = getItemBitstreamsServiceStub();
90+
itemBitstreamsService.getInitialBitstreamsPaginationOptions.and.returnValue(
91+
Object.assign(new PaginationComponentOptions(), {
92+
id: 'bundle-bitstreams-test',
93+
currentPage: 1,
94+
pageSize: 10,
95+
pageSizeOptions: [5, 10],
96+
}),
97+
);
8698

8799
TestBed.configureTestingModule({
88100
imports: [TranslateModule.forRoot(), ItemEditBitstreamBundleComponent],
@@ -93,6 +105,8 @@ describe('ItemEditBitstreamBundleComponent', () => {
93105
{ provide: RequestService, useValue: getMockRequestService() },
94106
{ provide: ItemBitstreamsService, useValue: itemBitstreamsService },
95107
{ provide: AuthorizationDataService, useValue: new AuthorizationDataServiceStub() },
108+
provideMockStore(),
109+
provideRouter([]),
96110
],
97111
schemas: [
98112
NO_ERRORS_SCHEMA,
@@ -108,14 +122,20 @@ describe('ItemEditBitstreamBundleComponent', () => {
108122
comp.columnSizes = columnSizes;
109123
comp.bundleUpdatesUrl = 'https://rest/api/core/items/item/bundles';
110124
viewContainerRef = (comp as any).viewContainerRef;
111-
spyOn(viewContainerRef, 'createEmbeddedView');
125+
spyOn(viewContainerRef, 'createEmbeddedView').and.callThrough();
112126
fixture.detectChanges();
113127
});
114128

115129
it('should create an embedded view of the component', () => {
116130
expect(viewContainerRef.createEmbeddedView).toHaveBeenCalled();
117131
});
118132

133+
it('should center the bundle action buttons', () => {
134+
const actionCell = fixture.debugElement.query(By.css('.bundle-row-actions'));
135+
expect(actionCell.classes['text-center']).toBeTrue();
136+
expect(actionCell.classes['text-end']).not.toBeTrue();
137+
});
138+
119139
describe('bundle removal', () => {
120140
it('removeBundle should register removal with object updates', () => {
121141
comp.removeBundle();
@@ -202,6 +222,15 @@ describe('ItemEditBitstreamBundleComponent', () => {
202222
});
203223

204224
describe('getRowClass', () => {
225+
it('should return \'table-danger\' when the bundle is marked for removal', () => {
226+
comp.bundleUpdate = {
227+
field: bundle,
228+
changeType: FieldChangeType.REMOVE,
229+
};
230+
231+
expect(comp.getRowClass(undefined, undefined)).toEqual('table-danger');
232+
});
233+
205234
it('should return \'table-info\' when the bitstream is the selected bitstream', () => {
206235
itemBitstreamsService.getSelectedBitstream.and.returnValue({
207236
bitstream: { id: 'bitstream-id' },

src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy {
444444
*/
445445
getRowClass(update: FieldUpdate, bitstream: BitstreamTableEntry): string {
446446
if (this.isMarkedForRemoval()) {
447-
return 'table-secondary';
447+
return 'table-danger';
448448
}
449449

450450
const selected = this.itemBitstreamsService.getSelectedBitstream();

0 commit comments

Comments
 (0)