diff --git a/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.html b/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.html index 8a2505391..c1d407481 100644 --- a/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.html +++ b/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.html @@ -56,6 +56,11 @@ }
- + +
diff --git a/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.spec.ts b/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.spec.ts new file mode 100644 index 000000000..1006ed35d --- /dev/null +++ b/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.spec.ts @@ -0,0 +1,56 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import type { TmdbEnrichedCastMember } from '@iptvnator/shared/interfaces'; +import { CastCrewRowComponent } from './cast-crew-row.component'; + +const ACTOR: TmdbEnrichedCastMember = { + name: 'Ada Vance', + character: 'Captain', + tmdbPersonId: 501, +} as TmdbEnrichedCastMember; + +describe('CastCrewRowComponent', () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CastCrewRowComponent, TranslateModule.forRoot()], + }).compileComponents(); + fixture = TestBed.createComponent(CastCrewRowComponent); + fixture.componentRef.setInput('cast', [ACTOR]); + }); + + function person(): HTMLElement { + const element = (fixture.nativeElement as HTMLElement).querySelector( + '.person' + ); + expect(element).not.toBeNull(); + return element as HTMLElement; + } + + it('offers a person with a TMDB id as a button that opens the actor page', () => { + const personSelected = jest.fn(); + fixture.componentInstance.personSelected.subscribe(personSelected); + fixture.detectChanges(); + + expect(person().getAttribute('role')).toBe('button'); + expect(person().getAttribute('tabindex')).toBe('0'); + person().click(); + expect(personSelected).toHaveBeenCalledWith(ACTOR); + }); + + it('renders plain entries for a host without an actor page', () => { + const personSelected = jest.fn(); + fixture.componentInstance.personSelected.subscribe(personSelected); + fixture.componentRef.setInput('interactive', false); + fixture.detectChanges(); + + // A focusable button that does nothing would mislead mouse and + // keyboard users alike. + expect(person().getAttribute('role')).toBeNull(); + expect(person().getAttribute('tabindex')).toBeNull(); + expect(person().classList.contains('person--clickable')).toBe(false); + person().click(); + expect(personSelected).not.toHaveBeenCalled(); + }); +}); diff --git a/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.ts b/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.ts index 3fe9df2d0..6adba7851 100644 --- a/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.ts +++ b/libs/ui/components/src/lib/detail-ui/cast-crew-row.component.ts @@ -22,7 +22,7 @@ export function scrollToCastCrewRow(): void { * "Cast & crew" row under the hero: 72px round avatars with the full name * and the character (or "Director"), the director first, initials when a * person has no photo. A click on a person with a TMDB id opens the actor - * page through the host. + * page through the host; a host without one renders plain entries. */ @Component({ selector: 'app-cast-crew-row', @@ -35,6 +35,8 @@ export class CastCrewRowComponent { readonly cast = input([]); readonly directors = input([]); readonly headingId = input('detail-cast-crew'); + /** False when the host has no actor page: nobody becomes a button that does nothing. */ + readonly interactive = input(true); readonly personSelected = output(); @@ -49,7 +51,7 @@ export class CastCrewRowComponent { ); isClickable(entry: CastCrewEntry): boolean { - return !!entry.member.tmdbPersonId; + return this.interactive() && !!entry.member.tmdbPersonId; } select(entry: CastCrewEntry): void {