Skip to content

Commit f48f36b

Browse files
VINOD KUMAR MURALIDHARANclaude
authored andcommitted
Address inline review comments from PR #11
chapter-detail.tsx: wrap allChapterNames in useMemo so memberConstraints, eventConstraints, and fundraisingConstraints only recompute when chapter or aliases actually change, preventing unnecessary useCollection re-subscriptions. use-firestore.ts: reset data/error/loading at the start of useDocument's effect so stale state is cleared immediately on every docId transition instead of persisting until the new snapshot arrives. data-table.spec.ts: - "third click removes sort": capture row state before first click and assert it matches after third click to verify the tri-state cycle - "can change page": coerce initialText to string (same null-safety fix as newText), and drop the weak || newText.length > 0 fallback so the assertion fails when pagination genuinely does not work - "columns can be resized": call test.fixme() when resize handle is absent rather than silently passing; assert widthChanged directly without the vacuous || newBox !== null fallback - Remove unused initialCount variable in "clearing search shows all results" fundraising.spec.ts: skip the archive test when search input is missing (to avoid acting on real campaigns) and scope the archiveButton locator to rows containing TEST_DATA_PREFIX instead of page-global .first() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5a077ae commit f48f36b

4 files changed

Lines changed: 58 additions & 37 deletions

File tree

pnaa/components/chapters/chapter-detail.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,16 @@ export function ChapterDetail({ chapterId }: { chapterId: string }) {
122122
);
123123

124124
// All chapter names to query (main + aliases)
125-
const allChapterNames = chapter?.name
126-
? [chapter.name, ...(aliases as AliasRow[]).map((a) => a.aliasName)]
127-
: [];
125+
// Wrapped in useMemo so the array reference is stable across renders,
126+
// preventing memberConstraints / eventConstraints / fundraisingConstraints
127+
// from recomputing (and re-subscribing) unless chapter or aliases actually change.
128+
const allChapterNames = useMemo(
129+
() =>
130+
chapter?.name
131+
? [chapter.name, ...(aliases as AliasRow[]).map((a) => a.aliasName)]
132+
: [],
133+
[chapter, aliases]
134+
);
128135

129136
const hasAliases = (aliases as AliasRow[]).length > 0;
130137

pnaa/e2e/local/data-table.spec.ts

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,20 @@ test.describe("Data Table - Column Sorting", () => {
7575
const header = page.locator("th").filter({ hasText: /name/i }).first();
7676

7777
if (await header.isVisible()) {
78-
// Click three times to cycle through asc -> desc -> none
78+
// Capture unsorted state before any clicks
79+
const initialRow = await page.locator("table tbody tr").first().textContent().catch(() => "");
80+
81+
// Cycle: first click (ascending), second click (descending), third click (removes sort)
7982
await header.click();
8083
await page.waitForTimeout(200);
8184
await header.click();
8285
await page.waitForTimeout(200);
8386
await header.click();
8487
await page.waitForTimeout(200);
8588

86-
// After third click, sort should be removed or cycled
87-
const hasSortIcon = await header.locator('button svg').count() > 0;
88-
expect(hasSortIcon).toBeTruthy();
89+
// After third click the sort is removed — row order must be back to initial
90+
const finalRow = await page.locator("table tbody tr").first().textContent().catch(() => "");
91+
expect(finalRow).toBe(initialRow);
8992
}
9093
});
9194
});
@@ -179,7 +182,7 @@ test.describe("Data Table - Pagination", () => {
179182
if (await nextButton.isVisible() && await nextButton.isEnabled()) {
180183
// Store first row content
181184
const firstRow = page.locator("table tbody tr").first();
182-
const initialText = await firstRow.textContent().catch(() => "");
185+
const initialText = (await firstRow.textContent().catch(() => "")) ?? "";
183186

184187
// Click next
185188
await nextButton.click();
@@ -190,7 +193,7 @@ test.describe("Data Table - Pagination", () => {
190193
const buttonDisabled = !(await nextButton.isEnabled());
191194
const contentChanged = newText !== initialText;
192195

193-
expect(contentChanged || buttonDisabled || newText.length > 0).toBeTruthy();
196+
expect(contentChanged || buttonDisabled).toBeTruthy();
194197
}
195198
});
196199

@@ -320,9 +323,6 @@ test.describe("Data Table - Filtering", () => {
320323
const searchInput = page.locator('input[placeholder*="search" i]').first();
321324

322325
if (await searchInput.isVisible()) {
323-
// Get initial count before any filtering
324-
const initialCount = await page.locator("table tbody tr").count();
325-
326326
// First filter with something unlikely to match
327327
await searchInput.fill("xyznonexistent999");
328328
await page.waitForTimeout(300);
@@ -358,25 +358,27 @@ test.describe("Data Table - Column Resizing", () => {
358358
'[data-testid="column-resize-handle"], .resize-handle, th [class*="resize"]'
359359
).first();
360360

361-
if (await resizeHandle.isVisible()) {
362-
// Get initial column width
363-
const header = page.locator("th").first();
364-
const initialBox = await header.boundingBox();
365-
366-
if (initialBox) {
367-
// Drag resize handle
368-
await resizeHandle.hover();
369-
await page.mouse.down();
370-
await page.mouse.move(initialBox.x + initialBox.width + 50, initialBox.y);
371-
await page.mouse.up();
372-
373-
// Width should have changed
374-
const newBox = await header.boundingBox();
361+
if (!(await resizeHandle.isVisible())) {
362+
// Resize handle not found — feature not yet implemented
363+
test.fixme();
364+
return;
365+
}
375366

376-
// Verify resize occurred (width changed) or resize feature works
377-
const widthChanged = newBox && newBox.width !== initialBox.width;
378-
expect(widthChanged || newBox !== null).toBeTruthy();
379-
}
367+
// Get initial column width
368+
const header = page.locator("th").first();
369+
const initialBox = await header.boundingBox();
370+
371+
if (initialBox) {
372+
// Drag resize handle
373+
await resizeHandle.hover();
374+
await page.mouse.down();
375+
await page.mouse.move(initialBox.x + initialBox.width + 50, initialBox.y);
376+
await page.mouse.up();
377+
378+
// Width must have changed for resize to be working
379+
const newBox = await header.boundingBox();
380+
const widthChanged = newBox !== null && newBox.width !== initialBox.width;
381+
expect(widthChanged).toBeTruthy();
380382
}
381383
});
382384
});

pnaa/e2e/local/fundraising.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,19 +307,23 @@ test.describe("Fundraising - CRUD Operations", () => {
307307
await page.goto("/fundraising");
308308
await page.waitForLoadState("networkidle");
309309

310-
// Filter to test data to avoid archiving real campaigns
310+
// Filter to test data rows — hard-skip if search is unavailable to avoid
311+
// accidentally targeting real campaigns.
311312
const searchInput = page.locator('input[placeholder*="search" i]').first();
312-
if (await searchInput.isVisible()) {
313-
await searchInput.fill(TEST_DATA_PREFIX);
314-
await page.waitForTimeout(500);
313+
if (!(await searchInput.isVisible())) {
314+
test.skip();
315+
return;
315316
}
317+
await searchInput.fill(TEST_DATA_PREFIX);
318+
await page.waitForTimeout(500);
316319

317320
// Get row count after filtering to test data
318321
const tableRows = page.locator("table tbody tr");
319322
const initialCount = await tableRows.count();
320323

321-
// Find an archive/delete button
322-
const archiveButton = page
324+
// Scope the archive button to rows that contain TEST_DATA_PREFIX only
325+
const testDataRow = page.locator("table tbody tr").filter({ hasText: TEST_DATA_PREFIX }).first();
326+
const archiveButton = testDataRow
323327
.locator('button:has-text("Archive"), button:has-text("Delete"), button[aria-label*="archive" i]')
324328
.first();
325329

pnaa/hooks/use-firestore.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ export function useDocument<T>(
1919
const [error, setError] = useState<Error | null>(null);
2020

2121
useEffect(() => {
22+
// Reset stale state immediately on every docId transition (including → undefined)
23+
// so consumers never see data/error from a previous document.
24+
// All three calls are batched by React 18 and are intentional here.
25+
// eslint-disable-next-line react-hooks/set-state-in-effect
26+
setData(null);
27+
setError(null);
28+
setLoading(Boolean(docId));
29+
2230
if (!docId) return;
2331

2432
const docRef = doc(db, collectionName, docId);
@@ -42,7 +50,7 @@ export function useDocument<T>(
4250
return () => unsubscribe();
4351
}, [collectionName, docId]);
4452

45-
return { data, loading: docId ? loading : false, error };
53+
return { data, loading, error };
4654
}
4755

4856
export function useCollection<T>(

0 commit comments

Comments
 (0)