Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ target
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Node / Web
node_modules/
dist/
build/
coverage/
.next/
*.log
.DS_Store
16 changes: 16 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@trustgraph/web",
"version": "0.1.0",
"private": true,
"description": "TrustGraph Web Dashboard and Contributor Profiles",
"main": "src/index.ts",
"scripts": {
"test": "jest"
},
"dependencies": {
"@trustgraph/types": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"license": "MIT"
}
86 changes: 86 additions & 0 deletions apps/web/src/components/AdminDashboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from "react";
import "@testing-library/jest-dom";
import { render, screen, fireEvent } from "@testing-library/react";
import AdminDashboard, { DEFAULT_MOCK_DATA } from "./AdminDashboard";
import * as csvExportUtils from "../utils/csvExport";

// Mock URL.createObjectURL and URL.revokeObjectURL for jsdom environment
if (typeof window !== "undefined") {
window.URL.createObjectURL = jest.fn(() => "blob:mock-url");
window.URL.revokeObjectURL = jest.fn();
}

describe("AdminDashboard Component (Issue #5)", () => {
beforeEach(() => {
jest.clearAllMocks();
});

test("renders admin dashboard with default mock reputation data", () => {
render(<AdminDashboard />);

expect(
screen.getByText("TrustGraph Steward DAO — Admin Dashboard")
).toBeInTheDocument();
expect(screen.getByTestId("export-csv-button")).toBeInTheDocument();
expect(screen.getByTestId("rankings-table")).toBeInTheDocument();
expect(screen.getByText("Alice Vance")).toBeInTheDocument();
expect(screen.getByText("Bob Stellar")).toBeInTheDocument();
});

test("triggers CSV export download on button click", () => {
const triggerSpy = jest
.spyOn(csvExportUtils, "triggerCsvDownload")
.mockImplementation(() => {});

const onExportSuccess = jest.fn();

render(<AdminDashboard onExportSuccess={onExportSuccess} />);

const exportBtn = screen.getByTestId("export-csv-button");
fireEvent.click(exportBtn);

expect(triggerSpy).toHaveBeenCalledTimes(1);
expect(triggerSpy).toHaveBeenCalledWith(
expect.stringContaining("Alice Vance"),
expect.stringContaining("trustgraph_reputation_trustgraph_steward_dao_2026-09.csv")
);

expect(onExportSuccess).toHaveBeenCalledWith(
expect.stringContaining("trustgraph_reputation_trustgraph_steward_dao_2026-09.csv"),
5
);

expect(screen.getByTestId("export-status-message")).toBeInTheDocument();
});

test("respects checkbox options for addresses and breakdown", () => {
const generateSpy = jest.spyOn(csvExportUtils, "generateReputationCsv");

render(<AdminDashboard />);

const addressCheckbox = screen.getByTestId("include-addresses-checkbox");
fireEvent.click(addressCheckbox); // toggle to false

const exportBtn = screen.getByTestId("export-csv-button");
fireEvent.click(exportBtn);

expect(generateSpy).toHaveBeenCalledWith(
expect.any(Array),
expect.objectContaining({
includeWalletAddresses: false,
})
);
});

test("switches period selector and updates rankings display", () => {
render(<AdminDashboard />);

const select = screen.getByTestId("period-select");
fireEvent.change(select, { target: { value: "2026-08" } });

// Mock data only has 2026-09 entries, so 2026-08 should show empty state
expect(
screen.getByText(/No contributor rankings recorded for 2026-08/i)
).toBeInTheDocument();
});
});
Loading