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();
+ });
+});