diff --git a/src/components/MarketplaceFilter/MarketplaceFilters.tsx b/src/components/MarketplaceFilter/MarketplaceFilters.tsx index bc714d38..50b181d7 100644 --- a/src/components/MarketplaceFilter/MarketplaceFilters.tsx +++ b/src/components/MarketplaceFilter/MarketplaceFilters.tsx @@ -182,6 +182,7 @@ const MarketplaceFilters = ({ /> {/* Draggable handle */}
{ + it('renders accessible filter controls and tracks sidebar search text', () => { + render(); + + const searchInput = screen.getByPlaceholderText('Search filters...'); + fireEvent.change(searchInput, { target: { value: 'balanced only' } }); + + expect(searchInput).toHaveValue('balanced only'); + expect(screen.getByRole('button', { name: /balanced/i })).toHaveAttribute( + 'aria-pressed', + 'true', + ); + expect(screen.getByLabelText('Maximum price')).toBeInTheDocument(); + expect(screen.getByLabelText('Maximum duration remaining')).toBeInTheDocument(); + expect(screen.getByLabelText('Minimum compliance score')).toBeInTheDocument(); + expect(screen.getByLabelText('Maximum loss threshold')).toBeInTheDocument(); + }); + + it('emits updated filters for type toggles, ranges, and reset', () => { + const onFilterChange = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: /safe/i })); + expect(onFilterChange).toHaveBeenLastCalledWith( + expect.objectContaining({ + commitmentType: ['balanced', 'aggressive', 'conservative'], + }), + ); + + fireEvent.change(screen.getByLabelText('Maximum price'), { + target: { value: '500000' }, + }); + expect(onFilterChange).toHaveBeenLastCalledWith( + expect.objectContaining({ priceRange: [0, 500000] }), + ); + + fireEvent.change(screen.getByLabelText('Maximum duration remaining'), { + target: { value: '45' }, + }); + expect(onFilterChange).toHaveBeenLastCalledWith( + expect.objectContaining({ durationRange: [0, 45] }), + ); + + fireEvent.change(screen.getByLabelText('Minimum compliance score'), { + target: { value: '70' }, + }); + expect(onFilterChange).toHaveBeenLastCalledWith( + expect.objectContaining({ minCompliance: 70 }), + ); + + fireEvent.change(screen.getByLabelText('Maximum loss threshold'), { + target: { value: '35' }, + }); + expect(onFilterChange).toHaveBeenLastCalledWith( + expect.objectContaining({ maxLoss: 35 }), + ); + + fireEvent.click(screen.getByRole('button', { name: /reset filters/i })); + expect(onFilterChange).toHaveBeenLastCalledWith(defaultFilters); + }); +}); diff --git a/tests/components/MarketplaceGrid.test.tsx b/tests/components/MarketplaceGrid.test.tsx new file mode 100644 index 00000000..31e05287 --- /dev/null +++ b/tests/components/MarketplaceGrid.test.tsx @@ -0,0 +1,74 @@ +// @vitest-environment happy-dom + +import '@testing-library/jest-dom/vitest'; +import { render, screen, within } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { MarketplaceGrid } from '../../src/components/MarketplaceGrid'; +import { MarketplaceGridSkeleton } from '../../src/components/MarketplaceGridSkeleton'; +import type { MarketplaceCardProps } from '../../src/components/MarketplaceCard'; + +vi.mock('../../src/components/modals/CommitmentDetailsModal', () => ({ + CommitmentDetailsModal: () => null, +})); + +const listings: MarketplaceCardProps[] = [ + { + id: '7', + type: 'Balanced', + score: 91, + amount: '$10,000', + duration: '45 days', + yield: '8.2%', + maxLoss: '12%', + owner: 'GABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + price: '$1,250', + forSale: true, + }, + { + id: '8', + type: 'Safe', + score: 87, + amount: '$5,000', + duration: '30 days', + yield: '4.5%', + maxLoss: '5%', + owner: 'GSHORT', + price: '$900', + forSale: false, + }, +]; + +describe('MarketplaceGrid', () => { + it('renders marketplace listing cards with trade and unavailable states', () => { + render(); + + const grid = screen.getByRole('region', { name: /marketplace listings/i }); + expect(within(grid).getByRole('article', { name: /commitment 7/i })).toBeInTheDocument(); + expect(within(grid).getByRole('article', { name: /commitment 8/i })).toBeInTheDocument(); + expect(screen.getByText('#CMT-007')).toBeInTheDocument(); + expect(screen.getByText('$10,000')).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /trade 7/i })).toHaveAttribute( + 'href', + '/marketplace/trade?id=7', + ); + expect(screen.getByText('Not for sale')).toBeInTheDocument(); + }); + + it('renders an empty state when no listings match the filters', () => { + render(); + + expect(screen.getByRole('region', { name: /marketplace listings/i })).toBeInTheDocument(); + expect(screen.getByText('No commitments available')).toBeInTheDocument(); + expect( + screen.getByText('New offers will appear here once they are listed.'), + ).toBeInTheDocument(); + }); + + it('renders the marketplace loading skeleton with the requested card count', () => { + render(); + + expect(screen.getByRole('status', { name: /loading marketplace listings/i })).toBeInTheDocument(); + const skeletonGrid = screen.getByRole('region', { name: /marketplace listings/i }); + expect(within(skeletonGrid).getAllByRole('listitem')).toHaveLength(3); + }); +});