All files / src/lib group-hierarchy.ts

100% Statements 21/21
90% Branches 9/10
100% Functions 7/7
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40            7x 394x 394x 558x   394x       7x 36x                 7x           26x 12x 76x 53x 53x   26x    
// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
 
import type { Channel, GroupHierarchyEntry } from "./types";
 
/** Each category once, in the order its first group appears, which is the order they are shown in. */
export const superCategoryNames = (entries: GroupHierarchyEntry[]): string[] => {
	const names = new Set<string>();
	for (const entry of entries) {
		if (entry.superCategory) names.add(entry.superCategory);
	}
	return [...names];
};
 
/** The provider's own groups for one content type, in channel order. */
export const groupTitles = (channels: Channel[], contentType: string): string[] => [
	...new Set(channels.filter((c) => c.contentType === contentType).map((c) => c.groupTitle)),
];
 
/**
 * Pairs each group with the names of its first few channels, so Gemini can
 * judge a group by what it holds and not only by its title. Only channels of
 * the given content type count, since a provider can reuse a group title
 * across live, movies and series.
 */
export const groupSamples = (
	channels: Channel[],
	groupNames: string[],
	contentType: string,
	perGroup: number
): [string, string[]][] => {
	const samples = new Map<string, string[]>(groupNames.map((g) => [g, []]));
	for (const c of channels) {
		if (c.contentType !== contentType) continue;
		const names = samples.get(c.groupTitle);
		if (names && names.length < perGroup) names.push(c.name);
	}
	return groupNames.map((g) => [g, samples.get(g) ?? []]);
};