Skip to content
Merged
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
4 changes: 2 additions & 2 deletions frontend/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@
"favoritesEmptyCta": "Tippen Sie ❤️ auf einem Produkt",
"newProducts": "Neue Produkte",
"newInCategory": "Neu in {category}",
"viewAll": "Alle anzeigen",
"viewHistory": "Verlauf anzeigen",
"viewAll": "Alle anzeigen",
"viewHistory": "Verlauf anzeigen",
"browse": "Durchsuchen →",
"scanned": "Gescannt",
"viewed": "Angesehen",
Expand Down
4 changes: 2 additions & 2 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@
"favoritesEmptyCta": "Tap \u2764\uFE0F on any product",
"newProducts": "New Products",
"newInCategory": "New {category}",
"viewAll": "View all",
"viewHistory": "View history",
"viewAll": "View all",
"viewHistory": "View history",
Comment on lines 118 to +120
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dashboard.viewAll is also used by RecentComparisons.tsx (link to /app/compare) which currently does not render an <ArrowRight> icon. After removing the from this translation, that “View all” link will lose its arrow entirely, reintroducing inconsistency on the dashboard. Consider adding the icon there as well, or splitting the i18n keys so only icon-suffixed links use the arrowless label.

Copilot uses AI. Check for mistakes.
"browse": "Browse →",
"scanned": "Scanned",
"viewed": "Viewed",
Expand Down
4 changes: 2 additions & 2 deletions frontend/messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@
"favoritesEmptyCta": "Kliknij \u2764\uFE0F przy produkcie",
"newProducts": "Nowe produkty",
"newInCategory": "Nowe {category}",
"viewAll": "Pokaż wszystko",
"viewHistory": "Historia",
"viewAll": "Pokaż wszystko",
"viewHistory": "Historia",
"browse": "Przeglądaj →",
"scanned": "Zeskanowane",
"viewed": "Przeglądane",
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/components/dashboard/CategoriesBrowse.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ describe("CategoriesBrowse", () => {
});
});

it("View All link includes an arrow icon (no text arrow)", async () => {
mockGetCategoryOverview.mockResolvedValue({
ok: true,
data: MOCK_CATEGORIES,
});
render(<CategoriesBrowse />, { wrapper: createWrapper() });
await vi.waitFor(() => {
const viewAll = screen.getAllByRole("link").find(
(l) => l.getAttribute("href") === "/app/categories",
);
expect(viewAll).toBeDefined();
expect(viewAll!.querySelector("svg")).toBeInTheDocument();
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test checks that a link contains an svg, but it doesn’t assert it’s the only icon within that link. To make the “no double arrow” intent more robust, consider asserting querySelectorAll("svg") has length 1 for the /app/categories link.

Suggested change
expect(viewAll!.querySelector("svg")).toBeInTheDocument();
const svgs = viewAll!.querySelectorAll("svg");
expect(svgs.length).toBe(1);
expect(svgs[0]).toBeInTheDocument();

Copilot uses AI. Check for mistakes.
expect(viewAll!.textContent).not.toContain("\u2192");
});
});

it("renders category icons for each chip", async () => {
mockGetCategoryOverview.mockResolvedValue({
ok: true,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/dashboard/CategoriesBrowse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { queryKeys, staleTimes } from "@/lib/query-keys";
import { createClient } from "@/lib/supabase/client";
import type { CategoryOverviewItem } from "@/lib/types";
import { useQuery } from "@tanstack/react-query";
import { ArrowRight } from "lucide-react";
import Link from "next/link";

function CategoryChip({
Expand Down Expand Up @@ -68,9 +69,10 @@ export function CategoriesBrowse() {
</h2>
<Link
href="/app/categories"
className="text-sm font-medium text-brand transition-colors hover:text-brand-hover"
className="inline-flex items-center gap-1 text-sm font-medium text-brand transition-colors hover:text-brand-hover"
>
{t("dashboard.viewAll")}
<ArrowRight className="h-3 w-3" aria-hidden="true" />
</Link>
</div>

Expand Down
9 changes: 8 additions & 1 deletion frontend/src/components/dashboard/NutritionTip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { NutritionTip } from "./NutritionTip";

// ─── Mocks ──────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -52,6 +52,13 @@ describe("NutritionTip", () => {
expect(link.getAttribute("href")).toMatch(/^\/learn\//);
});

it("learn more link uses icon arrow instead of text arrow", () => {
render(<NutritionTip />);
const link = screen.getByText(/Learn more/);
expect(link.querySelector("svg")).toBeInTheDocument();
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test verifies an svg exists, but it doesn’t assert that there’s exactly one icon in the link. If multiple SVGs get rendered inside the anchor, the test would still pass; consider asserting the SVG count is exactly 1.

Suggested change
expect(link.querySelector("svg")).toBeInTheDocument();
const svgs = link.querySelectorAll("svg");
expect(svgs).toHaveLength(1);

Copilot uses AI. Check for mistakes.
expect(link.textContent).not.toContain("\u2192");
});

it("learn more link points to a valid learn path", () => {
render(<NutritionTip />);
const link = screen.getByText(/Learn more/);
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/dashboard/NutritionTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

// ─── NutritionTip — cycling daily health tip card ───────────────────────────

import Link from "next/link";
import { useTranslation } from "@/lib/i18n";
import { qaStable } from "@/lib/qa-mode";
import { ArrowRight } from "lucide-react";
import Link from "next/link";

/** Total number of tips available in i18n files (dashboard.tip.0 … tip.N-1). */
const TIP_COUNT = 14;
Expand Down Expand Up @@ -67,9 +68,10 @@ export function NutritionTip() {
{learnHref && (
<Link
href={learnHref}
className="mt-1.5 inline-block text-xs font-medium text-brand hover:text-brand-hover transition-colors"
className="mt-1.5 inline-flex items-center gap-1 text-xs font-medium text-brand hover:text-brand-hover transition-colors"
>
{t("dashboard.tipLearnMore")} →
{t("dashboard.tipLearnMore")}
<ArrowRight className="h-3 w-3" aria-hidden="true" />
</Link>
)}
</div>
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/dashboard/RecentlyViewed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const map: Record<string, string> = {
"dashboard.recentlyViewedCompact": "Recently Viewed",
"dashboard.viewAll": "View all",
"dashboard.viewHistory": "View history",
"dashboard.viewHistory": "View history",
};
return map[key] ?? key;
},
Expand All @@ -20,7 +20,7 @@

vi.mock("next/image", () => ({
// eslint-disable-next-line @next/next/no-img-element
default: (props: React.ImgHTMLAttributes<HTMLImageElement>) => <img {...props} />,

Check warning on line 23 in frontend/src/components/dashboard/RecentlyViewed.test.tsx

View workflow job for this annotation

GitHub Actions / Typecheck & Lint

img elements must have an alt prop, either with meaningful text, or an empty string for decorative images
}));

// ─── Helpers ────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -133,6 +133,15 @@
expect(link).toHaveAttribute("href", "/app/search");
});

it("renders a single arrow icon in View history link (no text arrow)", () => {
const products = [makeProduct(1)];
render(<RecentlyViewed products={products} />);

const link = screen.getByRole("link", { name: /View history/ });
expect(link.querySelector("svg")).toBeInTheDocument();
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name says “single arrow icon”, but the assertion only checks that an svg exists. If a future change renders multiple SVGs inside the link (or reintroduces a double-icon bug), this test would still pass; consider asserting the SVG count is exactly 1 for this link.

Suggested change
expect(link.querySelector("svg")).toBeInTheDocument();
const svgs = link.querySelectorAll("svg");
expect(svgs).toHaveLength(1);

Copilot uses AI. Check for mistakes.
expect(link.textContent).not.toContain("\u2192");
});

it("has section aria-label", () => {
const products = [makeProduct(1)];
render(<RecentlyViewed products={products} />);
Expand Down
Loading