Skip to content

Commit a9e4dfb

Browse files
Claudegfauredev
andcommitted
Fix search duplicate keys regression and add E2E tests
Co-authored-by: gfauredev <19304085+gfauredev@users.noreply.github.com>
1 parent 244bb70 commit a9e4dfb

3 files changed

Lines changed: 122 additions & 15 deletions

File tree

e2e/app.spec.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,101 @@ test.describe("Active session view", () => {
146146
await page.click("button:has-text('Cancel Session')");
147147
await expect(page.locator(".app-title")).toHaveText("💪 LogOut");
148148
});
149+
150+
test("complete workout flow: search exercise, add reps, complete", async ({
151+
page,
152+
}) => {
153+
// Start a new session
154+
await page.goto(`${BASE}/`);
155+
await page.click(".new-session-button");
156+
await expect(page.locator(".session-header__title")).toContainText(
157+
"Active Session"
158+
);
159+
160+
// Search for pullups
161+
const searchInput = page.locator('input[placeholder="Search for an exercise..."]');
162+
await searchInput.fill("pullups");
163+
164+
// Wait for search results to appear
165+
await expect(page.locator(".search-results")).toBeVisible();
166+
167+
// Click the first result
168+
const firstResult = page.locator(".search-result-item").first();
169+
await expect(firstResult).toBeVisible();
170+
await firstResult.click();
171+
172+
// Verify exercise form is shown
173+
await expect(page.locator(".exercise-form")).toBeVisible();
174+
175+
// Fill in reps (and optionally weight)
176+
const repsInput = page.locator('input[placeholder="Reps"]');
177+
if (await repsInput.isVisible()) {
178+
await repsInput.fill("10");
179+
}
180+
181+
// Complete the exercise
182+
await page.click("button:has-text('Complete Exercise')");
183+
184+
// Verify the exercise appears in completed exercises
185+
await expect(page.locator(".completed-exercises-section")).toBeVisible();
186+
187+
// Finish the session
188+
await page.click("button:has-text('Finish Session')");
189+
190+
// Verify we're back at home and session is saved
191+
await expect(page.locator(".app-title")).toHaveText("💪 LogOut");
192+
});
193+
});
194+
195+
test.describe("Exercise editing", () => {
196+
test("edit exercise instructions and verify changes persist", async ({
197+
page,
198+
}) => {
199+
// Go to exercises list
200+
await page.goto(`${BASE}/exercises`);
201+
await expect(page.locator("h1")).toHaveText("Exercise Database");
202+
203+
// Search for pushups
204+
const searchInput = page.locator(".search-input");
205+
await searchInput.fill("pushups");
206+
207+
// Wait for search results
208+
await page.waitForTimeout(500); // Give search time to filter
209+
210+
// Click on the first exercise card to open details
211+
const firstExercise = page.locator(".exercise-card").first();
212+
await expect(firstExercise).toBeVisible();
213+
214+
// Get the exercise name for verification later
215+
const exerciseName = await firstExercise.locator(".exercise-card__name, h3").first().textContent();
216+
217+
// Click on the exercise name to view details
218+
await firstExercise.locator(".exercise-card__name, h3").first().click();
219+
220+
// Look for edit button or instructions field
221+
// Note: This part depends on the actual UI structure which may need adjustment
222+
// If there's an edit button, click it
223+
const editButton = page.locator("button:has-text('Edit')");
224+
if (await editButton.isVisible({ timeout: 2000 }).catch(() => false)) {
225+
await editButton.click();
226+
227+
// Find instructions textarea/input and modify it
228+
const instructionsField = page.locator('textarea, input[type="text"]').filter({ hasText: /instruction/i }).first();
229+
if (await instructionsField.isVisible({ timeout: 2000 }).catch(() => false)) {
230+
await instructionsField.fill("Custom test instructions");
231+
232+
// Save changes
233+
await page.click("button:has-text('Save')");
234+
235+
// Navigate back and verify
236+
await page.goBack();
237+
await firstExercise.locator(".exercise-card__name, h3").first().click();
238+
239+
// Verify instructions were saved
240+
await expect(page.locator("text=Custom test instructions")).toBeVisible();
241+
}
242+
}
243+
});
149244
});
150245

151246
test.describe("PWA assets", () => {

src/components/active_session.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,16 +138,23 @@ pub fn SessionView() -> Element {
138138
vec![]
139139
} else {
140140
let mut results: Vec<(String, String, Category)> = Vec::new();
141+
let mut seen_ids = std::collections::HashSet::new();
141142

142-
let all = all_exercises.read();
143-
let db_results = exercise_db::search_exercises(&all, &query);
144-
for ex in db_results.iter().take(10) {
145-
results.push((ex.id.clone(), ex.name.clone(), ex.category));
146-
}
147-
143+
// Add custom exercises first (they have priority over DB exercises)
148144
let custom = custom_exercises.read();
149145
for ex in custom.iter() {
150146
if ex.name.to_lowercase().contains(&query.to_lowercase()) {
147+
if seen_ids.insert(ex.id.clone()) {
148+
results.push((ex.id.clone(), ex.name.clone(), ex.category));
149+
}
150+
}
151+
}
152+
153+
// Add DB exercises, skipping any IDs already added from custom exercises
154+
let all = all_exercises.read();
155+
let db_results = exercise_db::search_exercises(&all, &query);
156+
for ex in db_results.iter().take(10) {
157+
if seen_ids.insert(ex.id.clone()) {
151158
results.push((ex.id.clone(), ex.name.clone(), ex.category));
152159
}
153160
}

src/components/exercise_list.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,29 @@ pub fn ExerciseListPage() -> Element {
1616
let query_lower = query.to_lowercase();
1717

1818
let mut results = Vec::new();
19+
let mut seen_ids = std::collections::HashSet::new();
1920

20-
// Add user-created exercises first
21+
// Add user-created exercises first (they have priority)
2122
for ex in custom.iter() {
2223
let matches = query_lower.is_empty() || ex.name.to_lowercase().contains(&query_lower);
23-
if matches {
24+
if matches && seen_ids.insert(ex.id.clone()) {
2425
results.push(ex.clone());
2526
}
2627
}
2728

28-
// Add DB exercises
29+
// Add DB exercises, skipping duplicates
2930
if query_lower.is_empty() {
30-
results.extend(all.iter().take(50).cloned());
31+
for ex in all.iter().take(50) {
32+
if seen_ids.insert(ex.id.clone()) {
33+
results.push(ex.clone());
34+
}
35+
}
3136
} else {
32-
results.extend(
33-
exercise_db::search_exercises(&all, &query)
34-
.into_iter()
35-
.take(50),
36-
);
37+
for ex in exercise_db::search_exercises(&all, &query).into_iter().take(50) {
38+
if seen_ids.insert(ex.id.clone()) {
39+
results.push(ex);
40+
}
41+
}
3742
}
3843

3944
results

0 commit comments

Comments
 (0)