diff --git a/src/resources/user_media_statistics.test.tsx b/src/resources/user_media_statistics.test.tsx
new file mode 100644
index 0000000..5fb31e4
--- /dev/null
+++ b/src/resources/user_media_statistics.test.tsx
@@ -0,0 +1,140 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import resource, { UserMediaStatsList, ListActions } from "./user_media_statistics";
+
+const {
+ mockedDataTable,
+ mockedCreatePath,
+ mockedUseListContext,
+ MockNumberField,
+ MockTextField,
+} = vi.hoisted(() => {
+ const MockNumberField = ({ source }) => {source};
+ const MockTextField = ({ source }) => {source};
+ return {
+ mockedDataTable: vi.fn(({ children, rowClick }) =>
rowClick("test-id")}>{children}
),
+ mockedCreatePath: vi.fn(),
+ mockedUseListContext: vi.fn(),
+ MockNumberField,
+ MockTextField
+ };
+});
+
+vi.mock("react-admin", async importOriginal => {
+ const actual = await importOriginal();
+ return {
+ ...actual,
+ List: ({ children, actions, filters, pagination, sort }) => (
+
+ {actions}
+ {filters}
+ {children}
+ {pagination}
+
+
+ ),
+ DataTable: Object.assign(mockedDataTable, {
+ Col: ({ source }) => {
+ if (source === 'media_count' || source === 'media_length') {
+ return ;
+ }
+ return ;
+ },
+ }),
+ NumberField: MockNumberField,
+ TextField: MockTextField,
+ SearchInput: () => ,
+ Pagination: () => ,
+ TopToolbar: ({ children }) => {children}
,
+ ExportButton: ({ disabled }) => ,
+ useCreatePath: () => mockedCreatePath,
+ useListContext: mockedUseListContext,
+ };
+});
+
+vi.mock("../components/media", () => ({
+ DeleteMediaButton: () =>
+}));
+
+
+describe("UserMediaStatsList", () => {
+ beforeEach(() => {
+ mockedCreatePath.mockClear();
+ });
+
+ it("should render the list with the correct columns and default sort", () => {
+ mockedUseListContext.mockReturnValue({
+ isLoading: false,
+ total: 1,
+ });
+ render();
+
+ expect(screen.getByTestId("list")).toBeTruthy();
+ expect(screen.getByTestId("data-table")).toBeTruthy();
+ expect(screen.getByTestId("search-input")).toBeTruthy();
+ expect(screen.getByTestId("pagination")).toBeTruthy();
+
+ const textFields = screen.getAllByTestId("text-field");
+ expect(textFields[0].textContent).toBe("user_id");
+ expect(textFields[1].textContent).toBe("displayname");
+
+ const numberFields = screen.getAllByTestId("number-field");
+ expect(numberFields[0].textContent).toBe("media_count");
+ expect(numberFields[1].textContent).toBe("media_length");
+
+ const sortInfo = screen.getByTestId("sort-info");
+ expect(sortInfo.getAttribute('data-field')).toBe('media_length');
+ expect(sortInfo.getAttribute('data-order')).toBe('DESC');
+ });
+
+ it("should handle row clicks", () => {
+ mockedUseListContext.mockReturnValue({
+ isLoading: false,
+ total: 1,
+ });
+ const mockPath = "/users/test-id/edit/media";
+ mockedCreatePath.mockReturnValue(mockPath);
+ render();
+
+ fireEvent.click(screen.getByTestId("data-table"));
+
+ expect(mockedCreatePath).toHaveBeenCalledWith({ resource: "users", id: "test-id", type: "edit" });
+ });
+});
+
+describe("ListActions", () => {
+ it("should disable export button when loading", () => {
+ mockedUseListContext.mockReturnValue({ isLoading: true, total: 1 });
+ render();
+ expect(screen.getByTestId("export-button").hasAttribute("disabled")).toBeTruthy();
+ });
+
+ it("should disable export button when total is 0", () => {
+ mockedUseListContext.mockReturnValue({ isLoading: false, total: 0 });
+ render();
+ expect(screen.getByTestId("export-button").hasAttribute("disabled")).toBeTruthy();
+ });
+
+ it("should enable export button when not loading and total is not 0", () => {
+ mockedUseListContext.mockReturnValue({ isLoading: false, total: 10 });
+ render();
+ expect(screen.getByTestId("export-button").hasAttribute("disabled")).toBeFalsy();
+ });
+
+ it("should render DeleteMediaButton", () => {
+ mockedUseListContext.mockReturnValue({ isLoading: false, total: 1 });
+ render();
+ expect(screen.getByTestId("delete-media-button")).toBeTruthy();
+ });
+});
+
+describe("recordRepresentation", () => {
+ it("should return displayname if present", () => {
+ const record = { user_id: "test_user", displayname: "Test User" };
+ expect(resource.recordRepresentation(record)).toBe("Test User");
+ });
+
+ it("should return user_id if displayname is not present", () => {
+ const record = { user_id: "test_user" };
+ expect(resource.recordRepresentation(record)).toBe("test_user");
+ });
+});
diff --git a/src/resources/user_media_statistics.tsx b/src/resources/user_media_statistics.tsx
index 1c864b6..106b74f 100644
--- a/src/resources/user_media_statistics.tsx
+++ b/src/resources/user_media_statistics.tsx
@@ -8,7 +8,6 @@ import {
Pagination,
ResourceProps,
SearchInput,
- TextField,
TopToolbar,
useCreatePath,
useListContext,
@@ -16,7 +15,7 @@ import {
import { DeleteMediaButton } from "../components/media";
-const ListActions = () => {
+export const ListActions = () => {
const { isLoading, total } = useListContext();
return (