From 6c69d7a5b31d7a52264dc4282347c0ab42ac16fc Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 18 Apr 2026 01:19:01 +0300 Subject: [PATCH] test(ComparisonView): cover parts of the widget with tests --- .../ui/Header/Header.svelte.test.ts | 46 ++++++++++ .../ui/Search/Search.svelte.test.ts | 32 +++++++ .../ui/Sidebar/Sidebar.svelte.test.ts | 91 +++++++++++++++++++ .../ui/Thumb/Thumb.svelte.test.ts | 42 +++++++++ 4 files changed, 211 insertions(+) create mode 100644 src/widgets/ComparisonView/ui/Header/Header.svelte.test.ts create mode 100644 src/widgets/ComparisonView/ui/Search/Search.svelte.test.ts create mode 100644 src/widgets/ComparisonView/ui/Sidebar/Sidebar.svelte.test.ts create mode 100644 src/widgets/ComparisonView/ui/Thumb/Thumb.svelte.test.ts diff --git a/src/widgets/ComparisonView/ui/Header/Header.svelte.test.ts b/src/widgets/ComparisonView/ui/Header/Header.svelte.test.ts new file mode 100644 index 0000000..346d7d0 --- /dev/null +++ b/src/widgets/ComparisonView/ui/Header/Header.svelte.test.ts @@ -0,0 +1,46 @@ +import { + fireEvent, + render, + screen, +} from '@testing-library/svelte'; +import Header from './Header.svelte'; + +const context = new Map([['responsive', { isMobile: false }]]); + +describe('Header', () => { + describe('Rendering', () => { + it('renders a header element', () => { + render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context }); + expect(document.querySelector('header')).toBeInTheDocument(); + }); + + it('shows "Open Config" title when sidebar is closed', () => { + render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context }); + expect(screen.getByTitle('Open Config')).toBeInTheDocument(); + }); + + it('shows "Close Config" title when sidebar is open', () => { + render(Header, { props: { isSidebarOpen: true, onSidebarToggle: () => {} }, context }); + expect(screen.getByTitle('Close Config')).toBeInTheDocument(); + }); + + it('renders a text input for the sample text', () => { + render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context }); + expect(screen.getByPlaceholderText('The quick brown fox...')).toBeInTheDocument(); + }); + + it('renders the theme toggle button', () => { + render(Header, { props: { isSidebarOpen: false, onSidebarToggle: () => {} }, context }); + expect(screen.getByTitle('Toggle theme')).toBeInTheDocument(); + }); + }); + + describe('Interaction', () => { + it('calls onSidebarToggle when toggle button is clicked', async () => { + const onSidebarToggle = vi.fn(); + render(Header, { props: { isSidebarOpen: false, onSidebarToggle }, context }); + await fireEvent.click(screen.getByTitle('Open Config')); + expect(onSidebarToggle).toHaveBeenCalledOnce(); + }); + }); +}); diff --git a/src/widgets/ComparisonView/ui/Search/Search.svelte.test.ts b/src/widgets/ComparisonView/ui/Search/Search.svelte.test.ts new file mode 100644 index 0000000..e5a89d2 --- /dev/null +++ b/src/widgets/ComparisonView/ui/Search/Search.svelte.test.ts @@ -0,0 +1,32 @@ +import { filterManager } from '$features/GetFonts'; +import { + render, + screen, +} from '@testing-library/svelte'; +import Search from './Search.svelte'; + +describe('Search', () => { + beforeEach(() => { + filterManager.queryValue = ''; + }); + + describe('Rendering', () => { + it('renders a text input', () => { + render(Search); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); + + it('has "Typeface Search" placeholder', () => { + render(Search); + expect(screen.getByPlaceholderText('Typeface Search')).toBeInTheDocument(); + }); + }); + + describe('Value binding', () => { + it('reflects filterManager.queryValue as initial value', () => { + filterManager.queryValue = 'Inter'; + render(Search); + expect(screen.getByRole('textbox')).toHaveValue('Inter'); + }); + }); +}); diff --git a/src/widgets/ComparisonView/ui/Sidebar/Sidebar.svelte.test.ts b/src/widgets/ComparisonView/ui/Sidebar/Sidebar.svelte.test.ts new file mode 100644 index 0000000..14b24ce --- /dev/null +++ b/src/widgets/ComparisonView/ui/Sidebar/Sidebar.svelte.test.ts @@ -0,0 +1,91 @@ +import { + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/svelte'; +import { createRawSnippet } from 'svelte'; +import { comparisonStore } from '../../model'; +import Sidebar from './Sidebar.svelte'; + +function textSnippet(text: string) { + return createRawSnippet(() => ({ render: () => `${text}` })); +} + +describe('Sidebar', () => { + afterEach(() => { + comparisonStore.side = 'A'; + }); + + describe('Rendering', () => { + it('renders the "Configuration" title', () => { + render(Sidebar); + expect(screen.getByText('Configuration')).toBeInTheDocument(); + }); + + it('renders Left Font and Right Font toggle buttons', () => { + render(Sidebar); + expect(screen.getByText('Left Font')).toBeInTheDocument(); + expect(screen.getByText('Right Font')).toBeInTheDocument(); + }); + + it('renders main snippet when provided', () => { + render(Sidebar, { props: { main: textSnippet('main-content') } }); + expect(screen.getByText('main-content')).toBeInTheDocument(); + }); + + it('renders controls snippet when provided', () => { + render(Sidebar, { props: { controls: textSnippet('controls-content') } }); + expect(screen.getByText('controls-content')).toBeInTheDocument(); + }); + + it('renders nothing in main area when no main snippet', () => { + const { container } = render(Sidebar); + expect(container.querySelector('[data-main]')).toBeNull(); + }); + }); + + describe('A/B toggle', () => { + it('Left Font button is active in side A mode', () => { + comparisonStore.side = 'A'; + render(Sidebar); + const leftBtn = screen.getByText('Left Font').closest('button'); + expect(leftBtn).toHaveClass('shadow-sm'); + }); + + it('Right Font button is not active in side A mode', () => { + comparisonStore.side = 'A'; + render(Sidebar); + const rightBtn = screen.getByText('Right Font').closest('button'); + expect(rightBtn).not.toHaveClass('shadow-sm'); + }); + + it('Right Font button is active in side B mode', () => { + comparisonStore.side = 'B'; + render(Sidebar); + const rightBtn = screen.getByText('Right Font').closest('button'); + expect(rightBtn).toHaveClass('shadow-sm'); + }); + + it('clicking Right Font switches to side B', async () => { + render(Sidebar); + expect(comparisonStore.side).toBe('A'); + await fireEvent.click(screen.getByText('Right Font')); + expect(comparisonStore.side).toBe('B'); + }); + + it('clicking Left Font switches back to side A from B', async () => { + comparisonStore.side = 'B'; + render(Sidebar); + await fireEvent.click(screen.getByText('Left Font')); + expect(comparisonStore.side).toBe('A'); + }); + + it('UI updates reactively after side switch', async () => { + render(Sidebar); + await fireEvent.click(screen.getByText('Right Font')); + const rightBtn = screen.getByText('Right Font').closest('button'); + await waitFor(() => expect(rightBtn).toHaveClass('shadow-sm')); + }); + }); +}); diff --git a/src/widgets/ComparisonView/ui/Thumb/Thumb.svelte.test.ts b/src/widgets/ComparisonView/ui/Thumb/Thumb.svelte.test.ts new file mode 100644 index 0000000..1449432 --- /dev/null +++ b/src/widgets/ComparisonView/ui/Thumb/Thumb.svelte.test.ts @@ -0,0 +1,42 @@ +import { render } from '@testing-library/svelte'; +import Thumb from './Thumb.svelte'; + +describe('Thumb', () => { + describe('Rendering', () => { + it('renders a container element', () => { + const { container } = render(Thumb, { sliderPos: 50, isDragging: false }); + expect(container.firstElementChild).toBeInTheDocument(); + }); + + it('applies sliderPos as left CSS style', () => { + const { container } = render(Thumb, { sliderPos: 30, isDragging: false }); + const el = container.firstElementChild as HTMLElement; + expect(el.style.left).toBe('30%'); + }); + + it('renders two handle squares', () => { + const { container } = render(Thumb, { sliderPos: 50, isDragging: false }); + const handles = container.querySelectorAll('.bg-brand.text-white'); + expect(handles).toHaveLength(2); + }); + }); + + describe('Dragging state', () => { + it('applies scale-110 to handles when dragging', () => { + const { container } = render(Thumb, { sliderPos: 50, isDragging: true }); + const handles = container.querySelectorAll('.scale-110'); + expect(handles.length).toBeGreaterThan(0); + }); + + it('applies scale-100 to handles when not dragging', () => { + const { container } = render(Thumb, { sliderPos: 50, isDragging: false }); + const handles = container.querySelectorAll('.scale-100'); + expect(handles.length).toBeGreaterThan(0); + }); + + it('does not apply scale-110 when not dragging', () => { + const { container } = render(Thumb, { sliderPos: 50, isDragging: false }); + expect(container.querySelector('.scale-110')).toBeNull(); + }); + }); +});