diff --git a/src/components/MethodChip.css b/src/components/MethodChip.css
index 1c4cb4c..108aebc 100644
--- a/src/components/MethodChip.css
+++ b/src/components/MethodChip.css
@@ -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;
@@ -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;
@@ -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%;
+ }
}
\ No newline at end of file
diff --git a/src/components/MethodChip.test.tsx b/src/components/MethodChip.test.tsx
new file mode 100644
index 0000000..3d7e0c7
--- /dev/null
+++ b/src/components/MethodChip.test.tsx
@@ -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();
+ expect(screen.getByLabelText("GET request")).toBeTruthy();
+ expect(screen.getByText("GET")).toBeTruthy();
+ });
+
+ it("renders an icon for the HTTP method", () => {
+ const { container } = render();
+ 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();
+ const chip = screen.getByLabelText("GET request");
+ expect(chip.getAttribute("tabindex")).toBe("0");
+ });
+
+ it("shows tooltip on focus and hides on blur", () => {
+ render();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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 = {
+ 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();
+ const chip = screen.getByLabelText(`${method} request`);
+ expect(chip.style.backgroundColor).toBe(colors.bg);
+ expect(chip.style.color).toBe(colors.fg);
+ unmount();
+ });
+ });
+});