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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ The interface does not execute contracts in the browser. Generated commands run

## Features

- **Design System & Theme Tokens**: Comprehensive CSS custom property scale (`:root`) standardizing color schemes (canvas, surfaces, accent, text hierarchy, borders) and spacing increments.
- **Design System & Theme Tokens**: Comprehensive CSS custom property scale (`:root`) standardizing color schemes (`--surface-base`, `--surface-elevated`, `--surface-terminal`, `--surface-output`, `--surface-card`, canvas, accent, text hierarchy, and `--border-subtle`, `--border-default`, `--border-strong`, `--border-input`) and spacing increments.
- **Compact Output for 320px Screens**: Responsive mobile output layout stacking the code block and action buttons on viewports down to 320px to prevent horizontal overflow and enhance touch targets.
- **Responsive Footer Layout**: Semantic multi-column footer with wrapping layout displaying brand description, workflow navigation links, and licensing metadata.
- **Workflow Input Reset**: Easily reset customized command builder inputs back to standard workflow defaults via the `Reset` action.
- **Module Filter & Empty State**: Search and filter testing modules by name or description with real-time feedback and an accessible empty-state layout with quick filter reset.
- **Responsive Layout Enhancements**:
- **Large-text Hero Layout**: Enhanced typography scaling at 1200px+ viewports for improved readability on large displays.
Expand Down
90 changes: 90 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,96 @@ describe("App command copy", () => {
});
});

describe("Reset behavior after customized inputs (#205)", () => {
it("resets coverage inputs back to defaults after customization", () => {
render(<App />);

const pathInput = screen.getByLabelText("Project or contract path") as HTMLInputElement;
const formatSelect = screen.getByLabelText(/report format/i) as HTMLSelectElement;
const openCheckbox = screen.getByLabelText(/open report when complete/i) as HTMLInputElement;

fireEvent.change(pathInput, { target: { value: "./contracts/escrow" } });
fireEvent.change(formatSelect, { target: { value: "lcov" } });
fireEvent.click(openCheckbox); // uncheck

expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit coverage ./contracts/escrow --format lcov",
);

const resetButton = screen.getByRole("button", { name: "Reset" });
fireEvent.click(resetButton);

expect(pathInput.value).toBe(".");
expect(formatSelect.value).toBe("html");
expect(openCheckbox.checked).toBe(true);
expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit coverage . --format html --open",
);
});

it("resets limits inputs back to defaults after customization", () => {
render(<App />);

fireEvent.click(screen.getByRole("button", { name: "limits" }));

const pathInput = screen.getByLabelText("Project or contract path") as HTMLInputElement;
const fnInput = screen.getByLabelText("Function") as HTMLInputElement;
const rampInput = screen.getByLabelText("Ramp parameter") as HTMLInputElement;

fireEvent.change(pathInput, { target: { value: "./custom.wasm" } });
fireEvent.change(fnInput, { target: { value: "custom_fn" } });
fireEvent.change(rampInput, { target: { value: "custom_ramp" } });

expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit limits --contract ./custom.wasm --fn custom_fn --ramp custom_ramp",
);

fireEvent.click(screen.getByRole("button", { name: "Reset" }));

expect(pathInput.value).toBe(".");
expect(fnInput.value).toBe("batch_payout");
expect(rampInput.value).toBe("recipients");
expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit limits --contract . --fn batch_payout --ramp recipients",
);
});

it("resets audit inputs back to defaults after customization", () => {
render(<App />);

fireEvent.click(screen.getByRole("button", { name: "audit" }));

const pathInput = screen.getByLabelText("Project or contract path") as HTMLInputElement;
const strictCheckbox = screen.getByLabelText(/fail on warnings/i) as HTMLInputElement;

fireEvent.change(pathInput, { target: { value: "./contracts/vault" } });
fireEvent.click(strictCheckbox); // uncheck strict

expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit audit ./contracts/vault",
);

fireEvent.click(screen.getByRole("button", { name: "Reset" }));

expect(pathInput.value).toBe(".");
expect(strictCheckbox.checked).toBe(true);
expect(screen.getByTestId("command-output")).toHaveTextContent(
"soroban-testkit audit . --strict",
);
});
});

describe("Responsive footer layout (#195)", () => {
it("renders wrapping footer columns with links and metadata", () => {
render(<App />);

expect(screen.getByText("Soroban Testkit", { selector: ".footer-brand" })).toBeInTheDocument();
expect(screen.getByText("Built for the Stellar developer ecosystem.")).toBeInTheDocument();
expect(screen.getByText("Apache-2.0")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "CLI Workbench" })).toBeInTheDocument();
});
});

describe("Module filter and empty-state layout (#188)", () => {
it("renders all 7 modules by default", () => {
render(<App />);
Expand Down
67 changes: 47 additions & 20 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,18 @@ export default function App() {

const generated = useMemo(() => buildCommand(options), [options]);

const handleReset = () => {
setPath(".");
setFormat("html");
setOpen(true);
setFn("batch_payout");
setRamp("recipients");
setStrict(true);
setShell("bash");
setCoverageOrder(defaultCoverageOrder);
setMultilineEditor(false);
};

const toggleAdvancedEditor = () => {
if (multilineEditor) {
setMultilineEditor(false);
Expand Down Expand Up @@ -1497,11 +1509,16 @@ export default function App() {

<div className="output">
<code data-testid="command-output">{highlightCommand(generated)}</code>
<button onClick={copy}>{copied ? "Copied" : "Copy"}</button>
{/* #146 — download action for generated shell script */}
<button onClick={downloadCommand} className="button-download" aria-label="Download command as shell script">
Download
</button>
<div className="output-actions">
<button type="button" className="button-reset" onClick={handleReset}>
Reset
</button>
<button type="button" onClick={copy}>{copied ? "Copied" : "Copy"}</button>
{/* #146 — download action for generated shell script */}
<button type="button" onClick={downloadCommand} className="button-download" aria-label="Download command as shell script">
Download
</button>
</div>
</div>

{/* Issue #144 & #145: Additional copy options */}
Expand Down Expand Up @@ -2363,21 +2380,31 @@ soroban-testkit = "0.2.0"`}</code>
</section>

<footer>
<span>Built for the Stellar developer ecosystem.</span>
<span className="footer-links">
<a href={CORE_REPO_URL}>Soroban Testkit core repository</a>
{/* #119 — changelog link */}
<a href={`${CORE_REPO_URL}/releases`}>Changelog</a>
{/* #174 — release notes link */}
<button type="button" className="footer-a11y-link" onClick={() => setReleaseNotesOpen(true)}>
Release notes
</button>
{/* #116 — accessibility statement */}
<button type="button" className="footer-a11y-link" onClick={() => setA11yOpen(true)}>
Accessibility statement
</button>
</span>
<span>Apache-2.0</span>
<div className="footer-col">
<span className="footer-brand">Soroban Testkit</span>
<p>Built for the Stellar developer ecosystem.</p>
</div>
<div className="footer-col">
<span className="footer-heading">Workflows</span>
<div className="footer-links">
<a href="#commands">CLI Workbench</a>
<a href={CORE_REPO_URL}>Soroban Testkit core repository</a>
{/* #119 — changelog link */}
<a href={`${CORE_REPO_URL}/releases`}>Changelog</a>
{/* #174 — release notes link */}
<button type="button" className="footer-a11y-link" onClick={() => setReleaseNotesOpen(true)}>
Release notes
</button>
{/* #116 — accessibility statement */}
<button type="button" className="footer-a11y-link" onClick={() => setA11yOpen(true)}>
Accessibility statement
</button>
</div>
</div>
<div className="footer-col footer-meta">
<span>Apache-2.0</span>
<span>Deterministic contract testing</span>
</div>
</footer>

{/* #174 — release notes dialog */}
Expand Down
119 changes: 117 additions & 2 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@
--font-sans: Manrope, sans-serif;
--font-mono: "DM Mono", monospace;

/* Shared Surface Tokens (#197) */
--surface-base: #061310;
--surface-elevated: #091916;
--surface-terminal: #07100e;
--surface-output: #020807;
--surface-card: #ffffff02;
--surface-card-hover: #66e2ad08;

/* Shared Border Tokens (#197) */
--border-subtle: #ffffff12;
--border-default: #ffffff1c;
--border-strong: #ffffff24;
--border-input: #ffffff1f;
--border-tab: #ffffff20;

/* Theme Colors */
--color-canvas: #061310;
--color-canvas-gradient: #123a31;
Expand All @@ -16,11 +31,22 @@

/* Accent Colors */
--color-accent: #66e2ad;
--color-accent-dark: #062017;
--color-accent-text-dark: #062017;
--color-accent-subtle: #66e2ad0b;
--color-accent-border: #66e2ad22;
--color-accent-surface: #66e2ad0d;

/* Text Hierarchy Tokens */
--text-primary: #e9f4ef;
--text-secondary: #a9bbb4;
--text-muted: #6f817a;
--text-body: #91a39c;
--text-label: #a7b8b1;
--text-tab: #8ea099;
--text-code: #dbe9e3;
--text-num: #587068;

/* Text Colors */
--color-text-primary: #e9f4ef;
--color-text-secondary: #a9bbb4;
Expand Down Expand Up @@ -305,9 +331,40 @@ input:invalid:focus, select:invalid:focus { border-color: #ff6b6b; box-shadow: 0
.check { display: flex; align-items: center; justify-content: space-between; }
.check-content { display: flex; align-items: center; gap: var(--space-sm); }
.check input { width: auto; accent-color: var(--color-accent); }

/* Command output and actions (#194, #205) */
.output { display: flex; align-items: center; gap: 15px; padding: var(--space-lg); background: var(--color-surface-output); border-radius: var(--radius-md); }
.output code { flex: 1; color: var(--color-accent); font: .75rem/1.6 var(--font-mono); overflow-wrap: anywhere; }
.output button { color: var(--color-canvas); background: var(--color-accent); border: 0; border-radius: var(--radius-xs); padding: var(--space-sm) var(--space-md); cursor: pointer; font-weight: 600; }
.output-actions { display: flex; align-items: center; gap: 10px; }
.output button { color: var(--color-canvas); background: var(--color-accent); border: 0; border-radius: var(--radius-xs); padding: var(--space-sm) var(--space-md); cursor: pointer; font-weight: 600; white-space: nowrap; }
.output button.button-reset { color: var(--text-secondary); background: transparent; border: 1px solid var(--border-strong); font-weight: 500; }
.output button.button-reset:hover { color: var(--text-primary); border-color: var(--color-accent); }

/* Compact command output layout for 320-pixel screens (#194) */
@media (max-width: 480px) {
.output {
flex-direction: column;
align-items: stretch;
gap: 12px;
padding: 12px;
}
.output code {
width: 100%;
font-size: .72rem;
line-height: 1.5;
word-break: break-all;
}
.output-actions {
display: flex;
gap: 8px;
width: 100%;
}
.output button {
flex: 1;
text-align: center;
padding: 10px;
}
}

/* Issue #144 & #145: Copy options styling */
.copy-options { display: flex; gap: var(--space-md); margin-block-start: var(--space-md); flex-wrap: wrap; }
Expand All @@ -320,7 +377,57 @@ input:invalid:focus, select:invalid:focus { border-color: #ff6b6b; box-shadow: 0

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }

footer { display: flex; justify-content: space-between; padding: 34px var(--page-padding); border-top: 1px solid var(--color-border-subtle); color: var(--color-text-muted); font-size: .78rem; }
/* Responsive footer layout with wrapping columns (#195) */
footer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
gap: 32px;
padding: 48px var(--page-padding);
border-top: 1px solid var(--color-border-subtle);
color: var(--color-text-muted);
font-size: .78rem;
}
.footer-col {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 150px;
}
.footer-brand {
color: var(--text-primary);
font-weight: 700;
font-size: .85rem;
}
.footer-col p {
margin: 0;
line-height: 1.5;
color: var(--text-body);
max-width: 280px;
}
.footer-heading {
color: var(--text-secondary);
font-size: .7rem;
font-family: "DM Mono", monospace;
text-transform: uppercase;
letter-spacing: .12em;
font-weight: 500;
}
.footer-links {
display: flex;
flex-direction: column;
gap: 6px;
}
.footer-links a:hover {
color: var(--color-accent);
}
.footer-meta {
display: flex;
flex-direction: column;
gap: 6px;
color: var(--text-muted);
}

/* Responsive adjustments */

Expand Down Expand Up @@ -1235,3 +1342,11 @@ footer { display: flex; justify-content: space-between; padding: 34px var(--page
margin-top: var(--space-md);
align-self: flex-start;
}

@media (max-width: 600px) {
footer {
flex-direction: column;
gap: 24px;
padding: 32px 22px;
}
}