From ac9ee0eb4e18be2da532074af1130127995ef474 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Mon, 18 May 2026 20:45:54 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20ProjectCard=20=E2=80=94=20add=20url=20p?= =?UTF-8?q?rop,=20RichText=20description,=20open=20link=20in=20new=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/ui/ProjectCard/ProjectCard.test.tsx | 17 ++++++++++++++--- .../project/ui/ProjectCard/ProjectCard.tsx | 12 ++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/entities/project/ui/ProjectCard/ProjectCard.test.tsx b/src/entities/project/ui/ProjectCard/ProjectCard.test.tsx index 266222d..0985915 100644 --- a/src/entities/project/ui/ProjectCard/ProjectCard.test.tsx +++ b/src/entities/project/ui/ProjectCard/ProjectCard.test.tsx @@ -6,6 +6,7 @@ const DEFAULT_PROPS = { year: '2024', description: 'A cool project description', tags: ['React', 'Node'], + url: 'https://example.com', }; describe('ProjectCard', () => { @@ -33,7 +34,17 @@ describe('ProjectCard', () => { it('renders the View Project button', () => { render(); - expect(screen.getByRole('button', { name: /view project/i })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /view project/i })).toBeInTheDocument(); + }); + + it('View Project link points to the project url', () => { + render(); + expect(screen.getByRole('link', { name: /view project/i })).toHaveAttribute('href', 'https://example.com'); + }); + + it('View Project link opens in a new tab', () => { + render(); + expect(screen.getByRole('link', { name: /view project/i })).toHaveAttribute('target', '_blank'); }); }); @@ -51,7 +62,7 @@ describe('ProjectCard', () => { it('View Project button is inside the sidebar column', () => { render(); - const btn = screen.getByRole('button', { name: /view project/i }); + const btn = screen.getByRole('link', { name: /view project/i }); expect(btn.closest('.brutal-border-sidebar')).toBeInTheDocument(); }); @@ -86,7 +97,7 @@ describe('ProjectCard', () => { it('View Project button uses sm size', () => { render(); - const btn = screen.getByRole('button', { name: /view project/i }); + const btn = screen.getByRole('link', { name: /view project/i }); expect(btn).toHaveClass('px-4', 'py-2', 'text-sm'); }); diff --git a/src/entities/project/ui/ProjectCard/ProjectCard.tsx b/src/entities/project/ui/ProjectCard/ProjectCard.tsx index f92267a..b1d7422 100644 --- a/src/entities/project/ui/ProjectCard/ProjectCard.tsx +++ b/src/entities/project/ui/ProjectCard/ProjectCard.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; import { cn } from '$shared/lib'; -import { Badge, Button, Card, CardSidebar, CardTitle } from '$shared/ui'; +import { Badge, Button, Card, CardSidebar, CardTitle, RichText } from '$shared/ui'; type Props = { /** @@ -19,6 +19,10 @@ type Props = { * Technology or category tags */ tags: string[]; + /** + * Project's URL + */ + url: string; /** * Optional preview image URL */ @@ -30,7 +34,7 @@ type Props = { * Sidebar: year badge, stack tags, View Project button. * Main: title, optional image, description. */ -export function ProjectCard({ title, year, description, tags, imageUrl }: Props) { +export function ProjectCard({ title, year, description, tags, url, imageUrl }: Props) { return ( )} - @@ -59,7 +63,7 @@ export function ProjectCard({ title, year, description, tags, imageUrl }: Props) {title} )} -

{description}

+