Skip to content

Commit 6779089

Browse files
authored
Merge pull request #1 from devrellabs/copilot/create-multi-select-breed-filter
Add multi-select breed filter dropdown component
2 parents 2991469 + 9b6c292 commit 6779089

5 files changed

Lines changed: 770 additions & 6 deletions

File tree

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Breed Filter Component', () => {
4+
test('should display breed filter button', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// Wait for page to load
8+
await page.waitForSelector('[id="breed-filter-button"]', { timeout: 10000 });
9+
10+
// Check that the filter button is visible
11+
const filterButton = page.locator('[id="breed-filter-button"]');
12+
await expect(filterButton).toBeVisible();
13+
await expect(filterButton).toContainText('Select breeds...');
14+
});
15+
16+
test('should open dropdown when filter button is clicked', async ({ page }) => {
17+
await page.goto('/');
18+
19+
// Wait for and click the filter button
20+
const filterButton = page.locator('[id="breed-filter-button"]');
21+
await filterButton.waitFor({ state: 'visible' });
22+
await filterButton.click();
23+
24+
// Check that dropdown panel is visible
25+
const dropdownPanel = page.locator('.dropdown-panel');
26+
await expect(dropdownPanel).toBeVisible();
27+
28+
// Check for search input
29+
const searchInput = page.locator('.search-input');
30+
await expect(searchInput).toBeVisible();
31+
await expect(searchInput).toHaveAttribute('placeholder', 'Search breeds...');
32+
});
33+
34+
test('should display breeds in the dropdown', async ({ page }) => {
35+
await page.goto('/');
36+
37+
// Open dropdown
38+
const filterButton = page.locator('[id="breed-filter-button"]');
39+
await filterButton.click();
40+
41+
// Wait for breeds to load
42+
await page.waitForSelector('.breed-item', { timeout: 10000 });
43+
44+
// Check that breed items are displayed
45+
const breedItems = page.locator('.breed-item');
46+
const count = await breedItems.count();
47+
expect(count).toBeGreaterThan(0);
48+
49+
// Check for edge case breeds
50+
await expect(page.locator('text=Mixed Breed')).toBeVisible();
51+
await expect(page.locator('text=Unknown')).toBeVisible();
52+
});
53+
54+
test('should allow selecting and deselecting breeds', async ({ page }) => {
55+
await page.goto('/');
56+
57+
// Open dropdown
58+
await page.locator('[id="breed-filter-button"]').click();
59+
60+
// Wait for breeds to load
61+
await page.waitForSelector('.breed-item', { timeout: 10000 });
62+
63+
// Select first breed
64+
const firstBreedItem = page.locator('.breed-item').first();
65+
const firstBreedName = await firstBreedItem.locator('.breed-name').textContent();
66+
await firstBreedItem.click();
67+
68+
// Check that checkbox is checked
69+
const checkbox = firstBreedItem.locator('.breed-checkbox');
70+
await expect(checkbox).toBeChecked();
71+
72+
// Check that button text updates
73+
const filterButton = page.locator('[id="breed-filter-button"]');
74+
await expect(filterButton).toContainText(firstBreedName || '');
75+
76+
// Deselect the breed
77+
await firstBreedItem.click();
78+
await expect(checkbox).not.toBeChecked();
79+
await expect(filterButton).toContainText('Select breeds...');
80+
});
81+
82+
test('should filter breeds using search input', async ({ page }) => {
83+
await page.goto('/');
84+
85+
// Open dropdown
86+
await page.locator('[id="breed-filter-button"]').click();
87+
await page.waitForSelector('.breed-item', { timeout: 10000 });
88+
89+
// Get initial count
90+
const initialCount = await page.locator('.breed-item').count();
91+
92+
// Type in search
93+
const searchInput = page.locator('.search-input');
94+
await searchInput.fill('Labrador');
95+
96+
// Wait a moment for filtering
97+
await page.waitForTimeout(300);
98+
99+
// Check that results are filtered
100+
const filteredCount = await page.locator('.breed-item').count();
101+
expect(filteredCount).toBeLessThan(initialCount);
102+
103+
// Check that visible breed contains search term
104+
const visibleBreed = await page.locator('.breed-item').first().locator('.breed-name').textContent();
105+
expect(visibleBreed?.toLowerCase()).toContain('labrador');
106+
});
107+
108+
test('should show empty state when no breeds match search', async ({ page }) => {
109+
await page.goto('/');
110+
111+
// Open dropdown
112+
await page.locator('[id="breed-filter-button"]').click();
113+
await page.waitForSelector('.breed-item', { timeout: 10000 });
114+
115+
// Type in search that won't match
116+
const searchInput = page.locator('.search-input');
117+
await searchInput.fill('NonexistentBreed12345');
118+
119+
// Wait for empty message
120+
const emptyMessage = page.locator('.empty-message');
121+
await expect(emptyMessage).toBeVisible();
122+
await expect(emptyMessage).toContainText('No breeds found matching');
123+
});
124+
125+
test('should show clear all button when breeds are selected', async ({ page }) => {
126+
await page.goto('/');
127+
128+
// Open dropdown
129+
await page.locator('[id="breed-filter-button"]').click();
130+
await page.waitForSelector('.breed-item', { timeout: 10000 });
131+
132+
// Select first breed
133+
await page.locator('.breed-item').first().click();
134+
135+
// Check that clear all button appears
136+
const clearAllButton = page.locator('.clear-all-button');
137+
await expect(clearAllButton).toBeVisible();
138+
139+
// Click clear all
140+
await clearAllButton.click();
141+
142+
// Check that button text resets
143+
const filterButton = page.locator('[id="breed-filter-button"]');
144+
await expect(filterButton).toContainText('Select breeds...');
145+
});
146+
147+
test('should close dropdown when clicking outside', async ({ page }) => {
148+
await page.goto('/');
149+
150+
// Open dropdown
151+
await page.locator('[id="breed-filter-button"]').click();
152+
const dropdownPanel = page.locator('.dropdown-panel');
153+
await expect(dropdownPanel).toBeVisible();
154+
155+
// Click outside
156+
await page.locator('h1').click();
157+
158+
// Check that dropdown is closed
159+
await expect(dropdownPanel).not.toBeVisible();
160+
});
161+
162+
test('should close dropdown when pressing Escape', async ({ page }) => {
163+
await page.goto('/');
164+
165+
// Open dropdown
166+
await page.locator('[id="breed-filter-button"]').click();
167+
const dropdownPanel = page.locator('.dropdown-panel');
168+
await expect(dropdownPanel).toBeVisible();
169+
170+
// Press Escape
171+
await page.keyboard.press('Escape');
172+
173+
// Check that dropdown is closed
174+
await expect(dropdownPanel).not.toBeVisible();
175+
});
176+
177+
test('should filter dogs when breeds are selected', async ({ page }) => {
178+
await page.goto('/');
179+
180+
// Wait for dogs to load
181+
await page.waitForSelector('.grid', { timeout: 10000 });
182+
183+
// Get initial dog count
184+
const initialDogCards = await page.locator('a[href^="/dog/"]').count();
185+
186+
// Open dropdown and select a specific breed
187+
await page.locator('[id="breed-filter-button"]').click();
188+
await page.waitForSelector('.breed-item', { timeout: 10000 });
189+
190+
// Select first non-edge-case breed
191+
const firstBreedItem = page.locator('.breed-item').first();
192+
await firstBreedItem.click();
193+
194+
// Close dropdown by clicking outside
195+
await page.locator('h1').click();
196+
197+
// Wait a moment for filtering
198+
await page.waitForTimeout(500);
199+
200+
// Get filtered dog count
201+
const filteredDogCards = await page.locator('a[href^="/dog/"]').count();
202+
203+
// Count should be less than or equal to initial (depending on how many dogs have that breed)
204+
expect(filteredDogCards).toBeLessThanOrEqual(initialDogCards);
205+
});
206+
207+
test('should be keyboard accessible', async ({ page }) => {
208+
await page.goto('/');
209+
210+
// Tab to the filter button
211+
await page.keyboard.press('Tab'); // Skip to first interactive element
212+
213+
// Find and focus the filter button
214+
const filterButton = page.locator('[id="breed-filter-button"]');
215+
await filterButton.focus();
216+
217+
// Press Enter to open
218+
await page.keyboard.press('Enter');
219+
220+
// Check that dropdown is open
221+
const dropdownPanel = page.locator('.dropdown-panel');
222+
await expect(dropdownPanel).toBeVisible();
223+
224+
// Press Escape to close
225+
await page.keyboard.press('Escape');
226+
await expect(dropdownPanel).not.toBeVisible();
227+
});
228+
229+
test('should have proper ARIA labels', async ({ page }) => {
230+
await page.goto('/');
231+
232+
// Check filter button ARIA attributes
233+
const filterButton = page.locator('[id="breed-filter-button"]');
234+
await expect(filterButton).toHaveAttribute('aria-haspopup', 'listbox');
235+
await expect(filterButton).toHaveAttribute('aria-label', 'Select dog breeds to filter');
236+
237+
// Open dropdown
238+
await filterButton.click();
239+
await expect(filterButton).toHaveAttribute('aria-expanded', 'true');
240+
241+
// Check dropdown ARIA attributes
242+
const dropdownPanel = page.locator('.dropdown-panel');
243+
await expect(dropdownPanel).toHaveAttribute('role', 'listbox');
244+
await expect(dropdownPanel).toHaveAttribute('aria-label', 'Breed selection');
245+
246+
// Check search input
247+
const searchInput = page.locator('.search-input');
248+
await expect(searchInput).toHaveAttribute('aria-label', 'Search for breeds');
249+
});
250+
});

0 commit comments

Comments
 (0)