Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
113267d
feat(rabe_connector): implement secure persistent caching for active …
emmyokolo2525-cyber Aug 28, 2026
bb10bda
fixed ci test
Benalex8797 Aug 31, 2026
be5973c
feat: gracefully catch user signature rejection exceptions in wallet_…
josephinejose-alt Aug 31, 2026
ad342a7
feat(loading-skeleton): ensure ARIA attribute compliance (issue #273)
Aug 31, 2026
e486d39
feat(loading-skeleton): render validation messages and alerts (issue …
Aug 31, 2026
3958a28
feat(dashboard-list): add Storybook stories (issue #271)
Aug 31, 2026
eb36271
test(dashboard-list): add comprehensive RTL tests (issue #272)
Aug 31, 2026
2695dd2
Merge main into #342 and fix five un-awaited waitFor calls
godamongstmen897 Sep 1, 2026
4291512
Merge pull request #342 from emmyokolo2525-cyber/feat/rabe-connector-…
godamongstmen897 Sep 1, 2026
231fa0b
Merge branch 'main' into feat/315-325-329-micro-animations-and-tests
godamongstmen897 Sep 1, 2026
50363a5
Merge branch 'main' into feat/273-loading-skeleton-a11y
godamongstmen897 Sep 1, 2026
e90e94a
Merge branch 'main' into feat/271-dashboard-list-stories
godamongstmen897 Sep 1, 2026
0f04726
Merge pull request #384 from Benalex8797/feat/315-325-329-micro-anima…
godamongstmen897 Sep 1, 2026
d7b9590
Merge pull request #386 from Junirezz/feat/273-loading-skeleton-a11y
godamongstmen897 Sep 1, 2026
9013775
Merge pull request #388 from Junirezz/feat/271-dashboard-list-stories
godamongstmen897 Sep 1, 2026
bf8b566
Merge branch 'refs/heads/pr/389' into work-389
godamongstmen897 Sep 1, 2026
4033810
Merge main into #389 and type the new fixtures
godamongstmen897 Sep 1, 2026
4cfebe6
Merge branch 'refs/heads/pr/385' into work-385
godamongstmen897 Sep 1, 2026
1d68d9d
Merge main into #385 and give the toast spy a concrete signature
godamongstmen897 Sep 1, 2026
872d352
Merge pull request #389 from Junirezz/feat/272-dashboard-list-tests
godamongstmen897 Sep 1, 2026
ff97841
Merge pull request #385 from josephinejose-alt/feature/wallet-disconn…
godamongstmen897 Sep 1, 2026
6026c38
Merge main into #387, dropping the re-encoded LoadingSkeleton
godamongstmen897 Sep 1, 2026
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
248 changes: 248 additions & 0 deletions __tests__/dark-mode-switcher.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import DarkModeSwitcher, {
DarkModeSwitcherEmptyState,
type DarkModeSwitcherProps,
} from "@/app/components/DarkModeSwitcher";

// ---------------------------------------------------------------------------
// Helper
// ---------------------------------------------------------------------------

function renderSwitcher(props: DarkModeSwitcherProps = {}) {
return render(<DarkModeSwitcher {...props} />);
}

// ---------------------------------------------------------------------------
// Empty state (isDarkMode is null / undefined)
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — empty state", () => {
it("renders empty state when isDarkMode is undefined", () => {
renderSwitcher();
expect(screen.getByTestId("dark-mode-switcher-empty-state")).toBeInTheDocument();
});

it("renders empty state when isDarkMode is null", () => {
renderSwitcher({ isDarkMode: null });
expect(screen.getByTestId("dark-mode-switcher-empty-state")).toBeInTheDocument();
});

it("does NOT render the toggle button in empty state", () => {
renderSwitcher();
expect(screen.queryByTestId("dark-mode-switcher")).not.toBeInTheDocument();
});

it("empty state has role=region", () => {
renderSwitcher();
expect(screen.getByTestId("dark-mode-switcher-empty-state")).toHaveAttribute(
"role",
"region"
);
});

it("empty state has descriptive aria-label", () => {
renderSwitcher();
expect(screen.getByTestId("dark-mode-switcher-empty-state")).toHaveAttribute(
"aria-label",
"No theme preferences"
);
});

it("DarkModeSwitcherEmptyState renders standalone", () => {
render(<DarkModeSwitcherEmptyState />);
expect(screen.getByTestId("dark-mode-switcher-empty-state")).toBeInTheDocument();
});

it("DarkModeSwitcherEmptyState accepts className", () => {
render(<DarkModeSwitcherEmptyState className="mt-4" />);
expect(screen.getByTestId("dark-mode-switcher-empty-state").className).toContain("mt-4");
});
});

// ---------------------------------------------------------------------------
// Loading state
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — loading state", () => {
it("renders loading state when loading=true", () => {
renderSwitcher({ loading: true });
expect(screen.getByTestId("dark-mode-switcher")).toBeInTheDocument();
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("data-state", "loading");
});

it("loading state has role=status", () => {
renderSwitcher({ loading: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("role", "status");
});

it("loading state has aria-label 'Loading theme'", () => {
renderSwitcher({ loading: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute(
"aria-label",
"Loading theme"
);
});

it("loading state shows loading text", () => {
renderSwitcher({ loading: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveTextContent("Loading theme...");
});

it("loading overrides the empty state — renders switcher not empty state", () => {
renderSwitcher({ loading: true, isDarkMode: undefined });
expect(screen.queryByTestId("dark-mode-switcher-empty-state")).not.toBeInTheDocument();
expect(screen.getByTestId("dark-mode-switcher")).toBeInTheDocument();
});
});

// ---------------------------------------------------------------------------
// Toggle — dark mode on
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — dark mode active", () => {
it("renders the toggle button", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher")).toBeInTheDocument();
});

it("has role=switch", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("role", "switch");
});

it("has aria-checked=true when dark", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("aria-checked", "true");
});

it("has data-state=dark", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("data-state", "dark");
});

it("defaults aria-label to 'Switch to light mode' when dark", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute(
"aria-label",
"Switch to light mode"
);
});

it("thumb is translated right when dark", () => {
renderSwitcher({ isDarkMode: true });
expect(screen.getByTestId("dark-mode-switcher-thumb")).toHaveClass("translate-x-5");
});
});

// ---------------------------------------------------------------------------
// Toggle — light mode
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — light mode active", () => {
it("has aria-checked=false when light", () => {
renderSwitcher({ isDarkMode: false });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("aria-checked", "false");
});

it("has data-state=light", () => {
renderSwitcher({ isDarkMode: false });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("data-state", "light");
});

it("defaults aria-label to 'Switch to dark mode' when light", () => {
renderSwitcher({ isDarkMode: false });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute(
"aria-label",
"Switch to dark mode"
);
});

it("thumb is at translate-x-0 when light", () => {
renderSwitcher({ isDarkMode: false });
expect(screen.getByTestId("dark-mode-switcher-thumb")).toHaveClass("translate-x-0");
});
});

// ---------------------------------------------------------------------------
// Interactions
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — interactions", () => {
it("calls onToggle on click", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();
renderSwitcher({ isDarkMode: true, onToggle });
await user.click(screen.getByTestId("dark-mode-switcher"));
expect(onToggle).toHaveBeenCalledTimes(1);
});

it("calls onToggle on Space key", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();
renderSwitcher({ isDarkMode: false, onToggle });
screen.getByTestId("dark-mode-switcher").focus();
await user.keyboard(" ");
expect(onToggle).toHaveBeenCalledTimes(1);
});

it("calls onToggle on Enter key", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();
renderSwitcher({ isDarkMode: false, onToggle });
screen.getByTestId("dark-mode-switcher").focus();
await user.keyboard("{Enter}");
expect(onToggle).toHaveBeenCalledTimes(1);
});

it("does NOT call onToggle when disabled", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();
renderSwitcher({ isDarkMode: true, onToggle, disabled: true });
await user.click(screen.getByTestId("dark-mode-switcher"));
expect(onToggle).not.toHaveBeenCalled();
});

it("does NOT call onToggle when loading", async () => {
const onToggle = vi.fn();
const user = userEvent.setup();

Check warning on line 209 in __tests__/dark-mode-switcher.test.tsx

View workflow job for this annotation

GitHub Actions / build

'user' is assigned a value but never used
renderSwitcher({ loading: true, onToggle });
// loading state is a span, not a button — no click interaction
expect(onToggle).not.toHaveBeenCalled();
});
});

// ---------------------------------------------------------------------------
// Props
// ---------------------------------------------------------------------------

describe("DarkModeSwitcher — props", () => {
it("accepts a custom ariaLabel", () => {
renderSwitcher({ isDarkMode: true, ariaLabel: "Toggle theme" });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute(
"aria-label",
"Toggle theme"
);
});

it("accepts a custom id", () => {
renderSwitcher({ isDarkMode: false, id: "my-switcher" });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("id", "my-switcher");
});

it("applies custom className to the button", () => {
renderSwitcher({ isDarkMode: false, className: "mt-4" });
expect(screen.getByTestId("dark-mode-switcher").className).toContain("mt-4");
});

it("disabled button has aria-disabled=true", () => {
renderSwitcher({ isDarkMode: true, disabled: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("aria-disabled", "true");
});

it("disabled button has tabIndex=-1", () => {
renderSwitcher({ isDarkMode: false, disabled: true });
expect(screen.getByTestId("dark-mode-switcher")).toHaveAttribute("tabindex", "-1");
});
});
64 changes: 34 additions & 30 deletions __tests__/dashboard-accessibility.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, waitFor } from "@testing-library/react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import Dashboard from "@/app/dashboard/page";

Expand Down Expand Up @@ -374,15 +374,17 @@ describe("Dashboard — accessibility compliance (ARIA, keyboard, semantic HTML)

render(<Dashboard />);

await waitFor(() => {
const expandButton = screen.getByRole("button", { name: /Job #job-1/i });
expandButton.click();
const expandButton = await screen.findByRole("button", {
name: /Job #job-1/i,
});
if (expandButton.getAttribute("aria-expanded") !== "true") {
fireEvent.click(expandButton);
}

waitFor(() => {
const region = screen.getByRole("region", { name: /Details for job #job-1/i });
expect(region).toBeInTheDocument();
});
const region = await screen.findByRole("region", {
name: /Details for job #job-1/i,
});
expect(region).toBeInTheDocument();
});

it("role badges have aria-label describing role", async () => {
Expand Down Expand Up @@ -467,11 +469,9 @@ describe("Dashboard — accessibility compliance (ARIA, keyboard, semantic HTML)

render(<Dashboard />);

waitFor(() => {
const status = screen.getByRole("status");
expect(status).toHaveAttribute("aria-live", "polite");
expect(status).toHaveTextContent("Connect your wallet");
});
const status = screen.getByRole("status");
expect(status).toHaveAttribute("aria-live", "polite");
expect(status).toHaveTextContent("Connect your wallet");
});
});

Expand Down Expand Up @@ -783,15 +783,17 @@ describe("Dashboard — accessibility compliance (ARIA, keyboard, semantic HTML)

render(<Dashboard />);

await waitFor(() => {
const expandButton = screen.getByRole("button", { name: /Job #job-1/i });
expandButton.click();
const expandButton = await screen.findByRole("button", {
name: /Job #job-1/i,
});
if (expandButton.getAttribute("aria-expanded") !== "true") {
fireEvent.click(expandButton);
}

waitFor(() => {
const milestonesRegion = screen.getByRole("region", { name: "Milestones" });
expect(milestonesRegion).toBeInTheDocument();
});
const milestonesRegion = await screen.findByRole("region", {
name: "Milestones",
});
expect(milestonesRegion).toBeInTheDocument();
});
});

Expand Down Expand Up @@ -1095,14 +1097,18 @@ describe("Dashboard — accessibility compliance (ARIA, keyboard, semantic HTML)

render(<Dashboard />);

await waitFor(() => {
const expandButton = screen.getByRole("button", { name: /Job #job-1/i });
expandButton.click();
const expandButton = await screen.findByRole("button", {
name: /Job #job-1/i,
});
if (expandButton.getAttribute("aria-expanded") !== "true") {
fireEvent.click(expandButton);
}

waitFor(() => {
const decorativeText = screen.queryByText("Collapse", { selector: "[aria-hidden='true']" });
expect(decorativeText).toBeInTheDocument();
await waitFor(() => {
const decorativeText = screen.queryByText("Collapse", {
selector: "[aria-hidden='true']",
});
expect(decorativeText).toBeInTheDocument();
});
});

Expand All @@ -1114,10 +1120,8 @@ describe("Dashboard — accessibility compliance (ARIA, keyboard, semantic HTML)

render(<Dashboard />);

waitFor(() => {
const status = screen.getByRole("status");
expect(status).toHaveAttribute("aria-live", "polite");
});
const status = screen.getByRole("status");
expect(status).toHaveAttribute("aria-live", "polite");
});

it("role=alert exposes error messages to screen readers immediately", async () => {
Expand Down
Loading
Loading