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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ A visual companion to [Soroban Testkit](https://github.com/soroban-testkit/testk

The interface does not execute contracts in the browser. Generated commands run locally through `soroban-testkit`, keeping contract tests deterministic and offline as required by the core project.

## Features

- **Theme Variants**: Supports both high-contrast dark theme (default) and WCAG AA/AAA-compliant light theme variant with an accessible navigation toggle button.
- **High-Density Display Refinement**: Subpixel border rendering, crisp box-shadows, and antialiasing tailored for Retina / high-DPI displays.
- **Interactive Module Cards**: Keyboard-accessible (`tabIndex={0}`) module cards with interactive `:hover` and `:focus-visible` outline indicators and accessible descriptions.
- **Consistent Vertical Rhythm**: Standardized section spacing, header margins, and content gutters across all page sections (`hero`, `modules`, `commands`, `footer`).

## Development

```sh
Expand Down
55 changes: 55 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,58 @@ describe("App command copy", () => {
expect(await screen.findByRole("button", { name: "Copied" })).toBeInTheDocument();
});
});

describe("Light theme variant (#202)", () => {
it("defaults to dark theme and toggles to light theme with high contrast", () => {
const { container } = render(<App />);
const main = container.querySelector("main");
expect(main).toHaveAttribute("data-theme", "dark");

const toggleButton = screen.getByRole("button", {
name: "Switch to light theme",
});
expect(toggleButton).toBeInTheDocument();

fireEvent.click(toggleButton);
expect(main).toHaveAttribute("data-theme", "light");
expect(
screen.getByRole("button", { name: "Switch to dark theme" }),
).toBeInTheDocument();

fireEvent.click(toggleButton);
expect(main).toHaveAttribute("data-theme", "dark");
});
});

describe("Module card keyboard focus and accessibility (#191)", () => {
it("makes each module card keyboard focusable with descriptive accessible labels", () => {
render(<App />);
const articles = screen.getAllByRole("article");
expect(articles).toHaveLength(7);

articles.forEach((article) => {
expect(article).toHaveAttribute("tabindex", "0");
expect(article).toHaveAttribute("aria-label");
});

const firstCard = articles[0];
firstCard.focus();
expect(document.activeElement).toBe(firstCard);
});
});

describe("Section rhythm and page structure (#192)", () => {
it("renders all page sections in a consistent vertical rhythm", () => {
const { container } = render(<App />);

const hero = container.querySelector("section.hero#top");
const modules = container.querySelector("section.modules#modules");
const commands = container.querySelector("section.commands#commands");
const footer = container.querySelector("footer");

expect(hero).toBeInTheDocument();
expect(modules).toBeInTheDocument();
expect(commands).toBeInTheDocument();
expect(footer).toBeInTheDocument();
});
});
22 changes: 20 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const descriptions: Record<CommandName, string> = {
};

export default function App() {
const [theme, setTheme] = useState<"dark" | "light">("dark");
const [command, setCommand] = useState<CommandName>("coverage");
const [path, setPath] = useState(".");
const [format, setFormat] = useState<"html" | "lcov" | "text">("html");
Expand All @@ -29,8 +30,12 @@ export default function App() {
window.setTimeout(() => setCopied(false), 1600);
};

const toggleTheme = () => {
setTheme((prev) => (prev === "dark" ? "light" : "dark"));
};

return (
<main>
<main data-theme={theme}>
<nav>
<a className="brand" href="#top" aria-label="Soroban Testkit home">
<span className="brand-mark">S</span>
Expand All @@ -40,6 +45,15 @@ export default function App() {
<a href="#modules">Modules</a>
<a href="#commands">Commands</a>
<a href="https://github.com/soroban-testkit/testkit-blockchain">GitHub ↗</a>
<button
type="button"
className="theme-toggle"
onClick={toggleTheme}
aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
>
<span aria-hidden="true">{theme === "dark" ? "☀️" : "🌙"}</span>
<span>{theme === "dark" ? "Light" : "Dark"}</span>
</button>
</div>
</nav>

Expand Down Expand Up @@ -67,7 +81,11 @@ export default function App() {
<header><p className="eyebrow">One prelude. Seven focused modules.</p><h2>Everything a serious contract test needs.</h2></header>
<div className="module-grid">
{modules.map(([name, description], index) => (
<article key={name}>
<article
key={name}
tabIndex={0}
aria-label={`Module ${name}: ${description}`}
>
<span>0{index + 1}</span><h3>{name}</h3><p>{description}</p>
</article>
))}
Expand Down
Loading