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);
+ });
+});