Skip to content

Commit 396bbd4

Browse files
committed
Fix default image logic in ds-thumbnail
1 parent 05d1554 commit 396bbd4

2 files changed

Lines changed: 75 additions & 70 deletions

File tree

src/app/thumbnail/thumbnail.component.spec.ts

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,18 @@ describe('ThumbnailComponent', () => {
127127
});
128128

129129
const errorHandler = () => {
130-
let fallbackSpy;
130+
let setSrcSpy;
131131

132132
beforeEach(() => {
133-
fallbackSpy = spyOn(comp, 'showFallback').and.callThrough();
133+
// disconnect error handler to be sure it's only called once
134+
const img = fixture.debugElement.query(By.css('img.thumbnail-content'));
135+
img.nativeNode.onerror = null;
136+
137+
comp.ngOnChanges();
138+
setSrcSpy = spyOn(comp, 'setSrc').and.callThrough();
134139
});
135140

136141
describe('retry with authentication token', () => {
137-
beforeEach(() => {
138-
// disconnect error handler to be sure it's only called once
139-
const img = fixture.debugElement.query(By.css('img.thumbnail-content'));
140-
img.nativeNode.onerror = null;
141-
});
142-
143142
it('should remember that it already retried once', () => {
144143
expect(comp.retriedWithToken).toBeFalse();
145144
comp.errorHandler();
@@ -153,7 +152,7 @@ describe('ThumbnailComponent', () => {
153152

154153
it('should fall back to default', () => {
155154
comp.errorHandler();
156-
expect(fallbackSpy).toHaveBeenCalled();
155+
expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage);
157156
});
158157
});
159158

@@ -172,11 +171,9 @@ describe('ThumbnailComponent', () => {
172171

173172
if ((comp.thumbnail as RemoteData<Bitstream>)?.hasFailed) {
174173
// If we failed to retrieve the Bitstream in the first place, fall back to the default
175-
expect(comp.src$.getValue()).toBe(null);
176-
expect(fallbackSpy).toHaveBeenCalled();
174+
expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage);
177175
} else {
178-
expect(comp.src$.getValue()).toBe(CONTENT + '?authentication-token=fake');
179-
expect(fallbackSpy).not.toHaveBeenCalled();
176+
expect(setSrcSpy).toHaveBeenCalledWith(CONTENT + '?authentication-token=fake');
180177
}
181178
});
182179
});
@@ -189,8 +186,7 @@ describe('ThumbnailComponent', () => {
189186
it('should fall back to default', () => {
190187
comp.errorHandler();
191188

192-
expect(comp.src$.getValue()).toBe(null);
193-
expect(fallbackSpy).toHaveBeenCalled();
189+
expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage);
194190

195191
// We don't need to check authorization if we failed to retrieve the Bitstreamin the first place
196192
if (!(comp.thumbnail as RemoteData<Bitstream>)?.hasFailed) {
@@ -210,7 +206,7 @@ describe('ThumbnailComponent', () => {
210206
comp.errorHandler();
211207
expect(authService.isAuthenticated).not.toHaveBeenCalled();
212208
expect(fileService.retrieveFileDownloadLink).not.toHaveBeenCalled();
213-
expect(fallbackSpy).toHaveBeenCalled();
209+
expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage);
214210
});
215211
});
216212
};
@@ -263,21 +259,23 @@ describe('ThumbnailComponent', () => {
263259
comp.thumbnail = thumbnail;
264260
});
265261

266-
it('should display an image', () => {
267-
comp.ngOnChanges();
268-
fixture.detectChanges();
269-
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
270-
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
271-
});
262+
describe('if content can be loaded', () => {
263+
it('should display an image', () => {
264+
comp.ngOnChanges();
265+
fixture.detectChanges();
266+
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
267+
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
268+
});
272269

273-
it('should include the alt text', () => {
274-
comp.ngOnChanges();
275-
fixture.detectChanges();
276-
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
277-
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
270+
it('should include the alt text', () => {
271+
comp.ngOnChanges();
272+
fixture.detectChanges();
273+
const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement;
274+
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
275+
});
278276
});
279277

280-
describe('when there is no thumbnail', () => {
278+
describe('if content can\'t be loaded', () => {
281279
errorHandler();
282280
});
283281
});
@@ -296,36 +294,42 @@ describe('ThumbnailComponent', () => {
296294
};
297295
});
298296

299-
describe('when there is a thumbnail', () => {
297+
describe('if RemoteData succeeded', () => {
300298
beforeEach(() => {
301299
comp.thumbnail = createSuccessfulRemoteDataObject(thumbnail);
302300
});
303301

304-
it('should display an image', () => {
305-
comp.ngOnChanges();
306-
fixture.detectChanges();
307-
const image: HTMLElement = de.query(By.css('img')).nativeElement;
308-
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
309-
});
302+
describe('if content can be loaded', () => {
303+
it('should display an image', () => {
304+
comp.ngOnChanges();
305+
fixture.detectChanges();
306+
const image: HTMLElement = de.query(By.css('img')).nativeElement;
307+
expect(image.getAttribute('src')).toBe(thumbnail._links.content.href);
308+
});
310309

311-
it('should display the alt text', () => {
312-
comp.ngOnChanges();
313-
fixture.detectChanges();
314-
const image: HTMLElement = de.query(By.css('img')).nativeElement;
315-
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
310+
it('should display the alt text', () => {
311+
comp.ngOnChanges();
312+
fixture.detectChanges();
313+
const image: HTMLElement = de.query(By.css('img')).nativeElement;
314+
expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt);
315+
});
316316
});
317317

318-
describe('but it can\'t be loaded', () => {
318+
describe('if content can\'t be loaded', () => {
319319
errorHandler();
320320
});
321321
});
322322

323-
describe('when there is no thumbnail', () => {
323+
describe('if RemoteData failed', () => {
324324
beforeEach(() => {
325325
comp.thumbnail = createFailedRemoteDataObject();
326326
});
327327

328-
errorHandler();
328+
it('should show the default image', () => {
329+
comp.defaultImage = 'default/image.jpg';
330+
comp.ngOnChanges();
331+
expect(comp.src$.getValue()).toBe('default/image.jpg');
332+
});
329333
});
330334
});
331335
});

src/app/thumbnail/thumbnail.component.ts

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { FileService } from '../core/shared/file.service';
1212
/**
1313
* This component renders a given Bitstream as a thumbnail.
1414
* One input parameter of type Bitstream is expected.
15-
* If no Bitstream is provided, a HTML placeholder will be rendered instead.
15+
* If no Bitstream is provided, an HTML placeholder will be rendered instead.
1616
*/
1717
@Component({
1818
selector: 'ds-thumbnail',
@@ -75,11 +75,11 @@ export class ThumbnailComponent implements OnChanges {
7575
return;
7676
}
7777

78-
const thumbnail = this.bitstream;
79-
if (hasValue(thumbnail?._links?.content?.href)) {
80-
this.setSrc(thumbnail?._links?.content?.href);
78+
const src = this.contentHref;
79+
if (hasValue(src)) {
80+
this.setSrc(src);
8181
} else {
82-
this.showFallback();
82+
this.setSrc(this.defaultImage);
8383
}
8484
}
8585

@@ -95,30 +95,41 @@ export class ThumbnailComponent implements OnChanges {
9595
}
9696
}
9797

98+
private get contentHref(): string | undefined {
99+
if (this.thumbnail instanceof Bitstream) {
100+
return this.thumbnail?._links?.content?.href;
101+
} else if (this.thumbnail instanceof RemoteData) {
102+
return this.thumbnail?.payload?._links?.content?.href;
103+
}
104+
}
105+
98106
/**
99107
* Handle image download errors.
100108
* If the image can't be loaded, try re-requesting it with an authorization token in case it's a restricted Bitstream
101109
* Otherwise, fall back to the default image or a HTML placeholder
102110
*/
103111
errorHandler() {
104-
if (!this.retriedWithToken && hasValue(this.thumbnail)) {
112+
const src = this.src$.getValue();
113+
const thumbnail = this.bitstream;
114+
const thumbnailSrc = thumbnail?._links?.content?.href;
115+
116+
if (!this.retriedWithToken && hasValue(thumbnailSrc) && src === thumbnailSrc) {
105117
// the thumbnail may have failed to load because it's restricted
106118
// → retry with an authorization token
107119
// only do this once; fall back to the default if it still fails
108120
this.retriedWithToken = true;
109121

110-
const thumbnail = this.bitstream;
111122
this.auth.isAuthenticated().pipe(
112123
switchMap((isLoggedIn) => {
113-
if (isLoggedIn && hasValue(thumbnail)) {
124+
if (isLoggedIn) {
114125
return this.authorizationService.isAuthorized(FeatureID.CanDownload, thumbnail.self);
115126
} else {
116127
return observableOf(false);
117128
}
118129
}),
119130
switchMap((isAuthorized) => {
120131
if (isAuthorized) {
121-
return this.fileService.retrieveFileDownloadLink(thumbnail._links.content.href);
132+
return this.fileService.retrieveFileDownloadLink(thumbnailSrc);
122133
} else {
123134
return observableOf(null);
124135
}
@@ -130,27 +141,17 @@ export class ThumbnailComponent implements OnChanges {
130141
// Otherwise, fall back to the default image right now
131142
this.setSrc(url);
132143
} else {
133-
this.showFallback();
144+
this.setSrc(this.defaultImage);
134145
}
135146
});
136147
} else {
137-
this.showFallback();
138-
}
139-
}
140-
141-
/**
142-
* To be called when the requested thumbnail could not be found
143-
* - If the current src is not the default image, try that first
144-
* - If this was already the case and the default image could not be found either,
145-
* show an HTML placecholder by setting src to null
146-
*
147-
* Also stops the loading animation.
148-
*/
149-
showFallback() {
150-
if (this.src$.getValue() !== this.defaultImage) {
151-
this.setSrc(this.defaultImage);
152-
} else {
153-
this.setSrc(null);
148+
if (src !== this.defaultImage) {
149+
// we failed to get thumbnail (possibly retried with a token but failed again)
150+
this.setSrc(this.defaultImage);
151+
} else {
152+
// we have failed to retrieve the default image, fall back to the placeholder
153+
this.setSrc(null);
154+
}
154155
}
155156
}
156157

0 commit comments

Comments
 (0)