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
43 changes: 38 additions & 5 deletions src/components/MethodChip.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/* MethodChip.css */
/* MethodChip.css — Issue #744: mobile responsive layout */

/* ── Base (desktop/tablet) ─────────────────────────────────────────────── */
.method-chip {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 8px;
margin: 2px;
min-width: 60px; /* uniform width */
padding: 4px 10px;
margin: 3px;
min-width: 64px; /* uniform width */
min-height: 32px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
Expand All @@ -23,9 +26,14 @@
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.method-icon {
/* Icon inside the chip. Note: the component uses "method-chip-icon", not
the bare "method-icon" class — fixed in Issue #744. */
.method-chip-icon {
margin-right: 4px;
font-size: 0.85rem;
display: inline-flex;
align-items: center;
flex-shrink: 0;
}
.method-tooltip {
position: absolute;
Expand All @@ -52,4 +60,29 @@
border-width: 5px 5px 0 5px;
border-style: solid;
border-color: var(--tooltip-bg, #333) transparent transparent transparent;
}

/* ── Narrow mobile (≤375 px) — Issue #744 ────────────────────────────────
Enlarged tap targets, additional spacing, and responsive wrapping so chips
don't overflow the viewport. WCAG 2.5.5 minimum 44 × 44 px touch target. */
@media (max-width: 375px) {
.method-chip {
padding: 10px 14px;
margin: 4px;
min-width: 44px;
min-height: 44px;
font-size: 0.8125rem;
border-radius: 6px;
}

.method-chip-icon {
margin-right: 6px;
font-size: 1rem;
}

.method-tooltip {
font-size: 0.75rem;
padding: 6px 10px;
top: 115%;
}
}
101 changes: 101 additions & 0 deletions src/components/MethodChip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// @vitest-environment jsdom

import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { MethodChip } from "./MethodChip";

afterEach(cleanup);

describe("MethodChip", () => {
it("renders with the HTTP method text uppercased", () => {
render(<MethodChip method="get" />);
expect(screen.getByLabelText("GET request")).toBeTruthy();
expect(screen.getByText("GET")).toBeTruthy();
});

it("renders an icon for the HTTP method", () => {
const { container } = render(<MethodChip method="POST" />);
const chip = screen.getByLabelText("POST request");
const icon = chip.querySelector(".method-chip-icon");
expect(icon).toBeTruthy();
expect(icon?.querySelector("svg")).toBeTruthy();
});

it("is focusable via tabIndex={0}", () => {
render(<MethodChip method="GET" />);
const chip = screen.getByLabelText("GET request");
expect(chip.getAttribute("tabindex")).toBe("0");
});

it("shows tooltip on focus and hides on blur", () => {
render(<MethodChip method="PUT" />);
const chip = screen.getByLabelText("PUT request");

// No tooltip initially
expect(screen.queryByRole("tooltip")).toBeNull();

// Focus should show tooltip
fireEvent.focus(chip);
expect(screen.getByRole("tooltip")).toBeTruthy();

// Blur should hide tooltip
fireEvent.blur(chip);
expect(screen.queryByRole("tooltip")).toBeNull();
});

// ── Mobile layout — CSS-class contract (Issue #744) ─────────────────────
//
// jsdom does not evaluate @media rules, so we verify the *class contract*:
// the correct CSS classes are present so the @media breakpoint rules can
// apply in a real browser.

it("has .method-chip class for responsive styling", () => {
render(<MethodChip method="GET" />);
const chip = screen.getByLabelText("GET request");
expect(chip.classList.contains("method-chip")).toBe(true);
});

it("has the matching method-chip-icon class on the icon wrapper", () => {
render(<MethodChip method="GET" />);
const chip = screen.getByLabelText("GET request");
const icon = chip.querySelector(".method-chip-icon");
expect(icon).toBeTruthy();
});

it("renders known HTTP methods with dedicated colors", () => {
const methods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
methods.forEach((method) => {
const { unmount } = render(<MethodChip method={method} />);
const chip = screen.getByLabelText(`${method} request`);
// Colors should be set via CSS custom properties, never inline hex
expect(chip.style.backgroundColor).toMatch(/^var\(--method-/);
expect(chip.style.color).toMatch(/^var\(--method-/);
unmount();
});
});

it("falls back to default colors for unknown methods", () => {
render(<MethodChip method="OPTIONS" />);
const chip = screen.getByLabelText("OPTIONS request");
expect(chip.style.backgroundColor).toBe("var(--surface-soft)");
expect(chip.style.color).toBe("var(--text)");
});

it("uses design-token background for GET, POST, PUT, DELETE, PATCH", () => {
const checks: Record<string, { bg: string; fg: string }> = {
GET: { bg: "var(--method-get-bg)", fg: "var(--method-get-fg)" },
POST: { bg: "var(--method-post-bg)", fg: "var(--method-post-fg)" },
PUT: { bg: "var(--method-put-bg)", fg: "var(--method-put-fg)" },
DELETE: { bg: "var(--method-delete-bg)", fg: "var(--method-delete-fg)" },
PATCH: { bg: "var(--method-patch-bg)", fg: "var(--method-patch-fg)" },
};

Object.entries(checks).forEach(([method, colors]) => {
const { unmount } = render(<MethodChip method={method} />);
const chip = screen.getByLabelText(`${method} request`);
expect(chip.style.backgroundColor).toBe(colors.bg);
expect(chip.style.color).toBe(colors.fg);
unmount();
});
});
});
Loading