fix(xtream): scroll to the selected category (#1663)

This commit is contained in:
Kate Markson authored and GitHub committed 2026-09-26 21:59:09 +02:00
1 parent e39c854a41
commit e619a2be86
7 files changed
+224 -8

No files matched your search

@@ -61,7 +61,7 @@ export class WorkspaceContextCategoryViewComponent {
}
}
private readonly hostEl = inject(ElementRef<HTMLElement>);
private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef);
readonly categoryClicked = output<WorkspaceCategoryViewItem>();
@@ -74,12 +74,11 @@ export class WorkspaceContextCategoryViewComponent {
queueMicrotask(() => {
const container = this.hostEl.nativeElement;
const candidates = Array.from(
container.querySelectorAll('[data-category-id]')
) as HTMLElement[];
const selected = candidates.find(
(el) =>
el.dataset['categoryId'] === String(selectedCategory)
// Follow the rendered selection: Electron selects by SQLite
// ID, while data-category-id can contain a provider ID that
// coincides with a different row's SQLite ID.
const selected = container.querySelector<HTMLElement>(
'.category-item[aria-current="true"]'
);
if (!selected) {
return;
@@ -62,7 +62,9 @@ function getCategoryLabels(
describe('WorkspaceContextPanelComponent', () => {
let fixture: ComponentFixture<WorkspaceContextPanelComponent>;
const xtreamCategories = signal([
const xtreamCategories = signal<
Array<{ id: number; name: string; xtream_id?: number }>
>([
{ id: 1, name: 'News' },
{ id: 2, name: 'Sports' },
]);
@@ -323,6 +325,59 @@ describe('WorkspaceContextPanelComponent', () => {
);
});
it.each([
{ id: 7, name: 'Alpha', top: 520 },
{ id: 8, name: 'Zulu', top: 2420 },
])(
'scrolls to the selected local category $name when provider IDs collide after sorting',
async ({ id, name, top }) => {
fixture.componentRef.setInput('section', 'live');
xtreamSelectedTypeContentState.set('ready');
// SQLite IDs drive selection; provider IDs can belong to another row.
xtreamCategories.set([
{ id: 8, xtream_id: 7, name: 'Zulu' },
{ id: 50, xtream_id: 500, name: 'Middle' },
{ id: 7, xtream_id: 8, name: 'Alpha' },
]);
fixture.componentInstance.setCategorySortMode('name-asc');
fixture.detectChanges();
const container = fixture.nativeElement.querySelector(
'app-workspace-context-category-view'
) as HTMLElement;
Object.defineProperties(container, {
clientHeight: { value: 400 },
scrollHeight: { value: 3000 },
});
container.scrollTop = 500;
container.getBoundingClientRect = () =>
new DOMRect(0, 100, 200, 400);
container.scrollTo = jest.fn();
const rows = Array.from(
container.querySelectorAll<HTMLButtonElement>('.category-item')
);
rows.forEach((row, index) => {
row.getBoundingClientRect = () =>
new DOMRect(0, [300, 800, 2200][index], 200, 40);
});
const selected = rows.find((row) =>
row.textContent?.includes(name)
);
if (!selected) throw new Error(`Missing category ${name}`);
selected.click();
expect(xtreamStore.setSelectedCategory).toHaveBeenCalledWith(id);
xtreamSelectedCategoryId.set(id);
fixture.detectChanges();
await fixture.whenStable();
expect(selected.getAttribute('aria-current')).toBe('true');
expect(container.scrollTo).toHaveBeenCalledWith({
behavior: 'smooth',
top,
});
}
);
it('uses translated category sort labels and distinct mode icons', () => {
fixture.componentRef.setInput('section', 'vod');
xtreamSelectedTypeContentState.set('ready');