From 32b864f7d2e1bd70be42ede507a190d7f02b6742 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 23 Apr 2026 13:05:22 +0300 Subject: [PATCH] feat(Link): create reusable Link ui component --- src/shared/ui/Link/Link.stories.svelte | 96 ++++++++++++++++++++++++++ src/shared/ui/Link/Link.svelte | 45 ++++++++++++ src/shared/ui/Link/Link.svelte.test.ts | 87 +++++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 src/shared/ui/Link/Link.stories.svelte create mode 100644 src/shared/ui/Link/Link.svelte create mode 100644 src/shared/ui/Link/Link.svelte.test.ts diff --git a/src/shared/ui/Link/Link.stories.svelte b/src/shared/ui/Link/Link.stories.svelte new file mode 100644 index 0000000..6883e20 --- /dev/null +++ b/src/shared/ui/Link/Link.stories.svelte @@ -0,0 +1,96 @@ + + + + {#snippet template(args: ComponentProps)} + + Google Fonts + + {/snippet} + + + + {#snippet template(args: ComponentProps)} + + Google Fonts + {#snippet icon()} + + {/snippet} + + {/snippet} + + + + {#snippet template()} +
+ + Google Fonts + {#snippet icon()} + + {/snippet} + + + Fontshare + {#snippet icon()} + + {/snippet} + + + GitHub + {#snippet icon()} + + {/snippet} + +
+ {/snippet} +
diff --git a/src/shared/ui/Link/Link.svelte b/src/shared/ui/Link/Link.svelte new file mode 100644 index 0000000..653caab --- /dev/null +++ b/src/shared/ui/Link/Link.svelte @@ -0,0 +1,45 @@ + + + + + {@render children?.()} + {@render icon?.()} + diff --git a/src/shared/ui/Link/Link.svelte.test.ts b/src/shared/ui/Link/Link.svelte.test.ts new file mode 100644 index 0000000..3cffcdf --- /dev/null +++ b/src/shared/ui/Link/Link.svelte.test.ts @@ -0,0 +1,87 @@ +import { + render, + screen, +} from '@testing-library/svelte'; +import { createRawSnippet } from 'svelte'; +import Link from './Link.svelte'; + +/** + * Helper to create a plain text snippet + */ +function textSnippet(text: string) { + return createRawSnippet(() => ({ + render: () => `${text}`, + })); +} + +/** + * Helper to create an icon snippet + */ +function iconSnippet() { + return createRawSnippet(() => ({ + render: () => ``, + })); +} + +describe('Link', () => { + const defaultProps = { + href: 'https://fonts.google.com', + }; + + describe('Rendering', () => { + it('renders text content via children snippet', () => { + render(Link, { + props: { + ...defaultProps, + children: textSnippet('Google Fonts'), + }, + }); + expect(screen.getByText('Google Fonts')).toBeInTheDocument(); + }); + + it('renders as an anchor element with correct href', () => { + render(Link, { props: defaultProps }); + const link = screen.getByRole('link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', 'https://fonts.google.com'); + }); + + it('renders the icon when provided via snippet', () => { + const { container } = render(Link, { + props: { + ...defaultProps, + children: textSnippet('Google Fonts'), + icon: iconSnippet(), + }, + }); + const icon = container.querySelector('svg'); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveClass('lucide-arrow-up-right'); + }); + }); + + describe('Attributes', () => { + it('applies custom CSS classes', () => { + render(Link, { + props: { + ...defaultProps, + class: 'custom-class', + }, + }); + expect(screen.getByRole('link')).toHaveClass('custom-class'); + }); + + it('spreads additional anchor attributes', () => { + render(Link, { + props: { + ...defaultProps, + target: '_blank', + rel: 'noopener', + }, + }); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('target', '_blank'); + expect(link).toHaveAttribute('rel', 'noopener'); + }); + }); +});