mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
fix(portal): drop a poster that fails to load from the About block (#1673)
The watch-state About block rendered its poster whenever the URL was non-empty, so a URL that failed to load showed the browser's broken-image glyph with clipped alt text. The hero above already falls back on error. A failed URL now counts as missing, matching the block's own degradation rule. The failure is keyed by URL so a different poster gets a fresh attempt. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b855eb73b
commit
a0562d9f7c
3 files changed
+58
-3
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: fix
|
||||
area: portal
|
||||
---
|
||||
|
||||
The About section under the player no longer shows a broken image when a movie or series poster cannot be loaded; the poster is simply left out, as it is when the provider sends none.
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ContentAboutComponent } from './content-about.component';
|
||||
|
||||
describe('ContentAboutComponent', () => {
|
||||
let fixture: ComponentFixture<ContentAboutComponent>;
|
||||
|
||||
const poster = (): HTMLImageElement | null =>
|
||||
(fixture.nativeElement as HTMLElement).querySelector('.about__poster');
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ContentAboutComponent, TranslateModule.forRoot()],
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(ContentAboutComponent);
|
||||
fixture.componentRef.setInput('title', 'Movie Title');
|
||||
});
|
||||
|
||||
it('omits the poster when there is no URL', () => {
|
||||
fixture.detectChanges();
|
||||
expect(poster()).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain('Movie Title');
|
||||
});
|
||||
|
||||
it('drops a poster that fails to load and retries a new URL', () => {
|
||||
fixture.componentRef.setInput('posterUrl', 'https://img.test/a.jpg');
|
||||
fixture.detectChanges();
|
||||
expect(poster()?.getAttribute('src')).toBe('https://img.test/a.jpg');
|
||||
|
||||
poster()?.dispatchEvent(new Event('error'));
|
||||
fixture.detectChanges();
|
||||
expect(poster()).toBeNull();
|
||||
expect(fixture.nativeElement.textContent).toContain('Movie Title');
|
||||
|
||||
fixture.componentRef.setInput('posterUrl', 'https://img.test/b.jpg');
|
||||
fixture.detectChanges();
|
||||
expect(poster()?.getAttribute('src')).toBe('https://img.test/b.jpg');
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,9 @@ import { NgTemplateOutlet } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
TemplateRef,
|
||||
computed,
|
||||
input,
|
||||
signal,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -13,7 +15,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
* stays reachable while the player occupies the hero position.
|
||||
*
|
||||
* Degradation rule: anything missing simply is not rendered — no "N/A"
|
||||
* placeholders. Chips and credits are stamped from the host-provided
|
||||
* placeholders. A poster URL that fails to load counts as missing. Chips and credits are stamped from the host-provided
|
||||
* *appDetailTags / *appDetailMeta templates, so the host's own @if guards apply.
|
||||
*/
|
||||
@Component({
|
||||
@@ -24,12 +26,13 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
<section class="about">
|
||||
<h3 class="about__heading">{{ 'PORTALS.ABOUT' | translate }}</h3>
|
||||
<div class="about__body">
|
||||
@if (posterUrl()) {
|
||||
@if (visiblePosterUrl(); as poster) {
|
||||
<img
|
||||
class="about__poster"
|
||||
[src]="posterUrl()"
|
||||
[src]="poster"
|
||||
[alt]="title() ?? ''"
|
||||
loading="lazy"
|
||||
(error)="failedPosterUrl.set(poster)"
|
||||
/>
|
||||
}
|
||||
<div class="about__info">
|
||||
@@ -63,4 +66,11 @@ export class ContentAboutComponent {
|
||||
readonly description = input<string>();
|
||||
readonly tagsTemplate = input<TemplateRef<unknown> | null>(null);
|
||||
readonly metaTemplate = input<TemplateRef<unknown> | null>(null);
|
||||
|
||||
// Keyed by URL so a different poster gets its own load attempt.
|
||||
protected readonly failedPosterUrl = signal<string | undefined>(undefined);
|
||||
protected readonly visiblePosterUrl = computed(() => {
|
||||
const url = this.posterUrl();
|
||||
return url && url !== this.failedPosterUrl() ? url : undefined;
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user