diff --git a/src/components/Reviews/ReviewsList.tsx b/src/components/Reviews/ReviewsList.tsx index a6b29fd..974e87d 100644 --- a/src/components/Reviews/ReviewsList.tsx +++ b/src/components/Reviews/ReviewsList.tsx @@ -1,7 +1,8 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import RatingSummary from "./RatingSummary"; +import { AlertCircle, MessageSquarePlus, RefreshCw } from "lucide-react"; +import { RatingSummary } from "./RatingSummary"; interface Review { id: string; @@ -23,9 +24,17 @@ type VoteState = "up" | "down" | null; interface ReviewsListProps { reviews: Review[]; + isLoading?: boolean; + error?: string | null; + onRetry?: () => void; } -export default function ReviewsList({ reviews }: ReviewsListProps) { +export default function ReviewsList({ + reviews, + isLoading = false, + error = null, + onRetry, +}: ReviewsListProps) { const [sortBy, setSortBy] = useState("mostRecent"); const [reviewList, setReviewList] = useState(reviews); const [visibleCount, setVisibleCount] = useState(5); @@ -88,6 +97,48 @@ export default function ReviewsList({ reviews }: ReviewsListProps) { ); }; + if (isLoading) { + return ( +
+
+ {Array.from({ length: 3 }).map((_, idx) => ( +
+ ))} +
+ ); + } + + if (error) { + return ( +
+ +
+

Unable to load course reviews

+

{error}

+
+ {onRetry && ( + + )} +
+ ); + } + return (
-
- -
+ {sortedReviews.length > 0 && ( +
+ +
+ )} {sortedReviews.length === 0 ? ( -
-

No reviews yet.

+
+ +

No reviews yet

+

+ Be the first learner to share feedback and rate this course after enrolling. +

) : (
diff --git a/src/components/Reviews/__tests__/ReviewsList.test.tsx b/src/components/Reviews/__tests__/ReviewsList.test.tsx new file mode 100644 index 0000000..eb27782 --- /dev/null +++ b/src/components/Reviews/__tests__/ReviewsList.test.tsx @@ -0,0 +1,33 @@ +// @vitest-environment jsdom +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import ReviewsList from '../ReviewsList'; + +describe('ReviewsList empty, loading, and error states (#203)', () => { + it('renders loading skeleton when isLoading is true', () => { + render(); + expect(screen.getByTestId('reviews-loading')).toBeInTheDocument(); + }); + + it('renders error state with retry callback when error is provided', () => { + const onRetry = vi.fn(); + render( + + ); + expect(screen.getByTestId('reviews-error')).toBeInTheDocument(); + expect(screen.getByText('Failed to load course reviews')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /Retry/i })); + expect(onRetry).toHaveBeenCalledTimes(1); + }); + + it('renders empty state when reviews list is empty', () => { + render(); + expect(screen.getByTestId('reviews-empty')).toBeInTheDocument(); + expect(screen.getByText('No reviews yet')).toBeInTheDocument(); + }); +});