All files / src/components RatingWithCount.tsx

100% Statements 29/29
80% Branches 12/15
100% Functions 3/3
100% Lines 29/29

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 41 42 43 44 45 461x                 42x 42x 42x   1x 42x 42x 42x 42x 42x   42x 1x 1x   42x 42x 42x 42x   1x 39x 39x 39x   39x   39x 39x 39x 39x   39x 39x   39x  
import { useTranslation } from 'react-i18next';
 
interface RatingWithCountProps {
  score: number | null | undefined;
  count?: number | null;
  className?: string;
  suffix?: string;
}
 
function normalizeCount(count: number | null | undefined): number | null {
  return typeof count === 'number' && Number.isFinite(count) ? Math.max(0, count) : null;
}
 
export function formatRatingWithCountText({
  score,
  count,
  suffix,
}: RatingWithCountProps): string {
  const normalizedCount = normalizeCount(count);
 
  if (score == null || !Number.isFinite(score)) {
    return '';
  }
 
  const countText = normalizedCount == null ? '' : ` (${normalizedCount})`;
  const suffixText = suffix ? ` ${suffix}` : '';
  return `★ ${score.toFixed(1)}${countText}${suffixText}`;
}
 
export function RatingWithCount(props: RatingWithCountProps) {
  const { t } = useTranslation();
  const text = formatRatingWithCountText(props);
  if (!text) return null;
 
  const ariaDetail = text.replace(/★\s*/u, '').trim();
 
  return (
    <span
      className={`rating-with-count ${props.className ?? ''}`.trim()}
      aria-label={t('rating.aria_label', { detail: ariaDetail })}
    >
      {text}
    </span>
  );
}