fix: implement fetch generation counter to handle concurrent refreshes in ScrollingList
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Has been cancelled

- Added a fetch generation counter to the fetchItems method to discard stale results from concurrent fetches.
- Updated the loading state management to ensure it only updates if the fetch is the latest.
- Enhanced the refresh method to prevent stale data from being rendered when multiple refreshes occur.
- Added a test case to verify that stale fetches are discarded correctly when concurrent refreshes happen.
- Fixed CSS for mobile banners to prevent overflow and adjusted font sizes for better visibility.
- Resolved issues with task icons disappearing when extending time on overdue chores by ensuring only one fetch is active at a time.
This commit is contained in:
2026-03-25 15:30:00 -04:00
parent f64311689b
commit 359c170b27
15 changed files with 166 additions and 35 deletions

View File

@@ -74,6 +74,15 @@ test.describe('Chore activation', () => {
await request.put(`/api/child/${childId}/set-tasks`, {
data: { task_ids: [kindId], type: 'kindness' },
})
// Add a schedule so COMPLETED status persists after trigger
await request.put(`/api/child/${childId}/task/${choreId}/schedule`, {
data: {
mode: 'interval',
interval_days: 1,
anchor_date: new Date().toISOString().slice(0, 10),
interval_has_deadline: false,
},
})
})
test.afterAll(async ({ request }) => {

View File

@@ -66,7 +66,7 @@ test.describe('Chore assignment', () => {
await expect(page.getByRole('button', { name: 'Assign Chores' })).toBeVisible()
await page.getByRole('button', { name: 'Assign Chores' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Chores' })).toBeVisible()
@@ -83,7 +83,7 @@ test.describe('Chore assignment', () => {
test('Select chores and submit — items appear in ParentView chores section', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Chores' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Chores' })).toBeVisible()
// Select two chores
@@ -126,7 +126,7 @@ test.describe('Chore assignment', () => {
test('Uncheck all chores and submit — items are removed from ParentView', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Chores' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Chores' })).toBeVisible()
// Uncheck the two previously assigned chores
@@ -151,7 +151,7 @@ test.describe('Chore assignment', () => {
test('Cancel does not persist selection changes', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Chores' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-chores(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Chores' })).toBeVisible()
// Check an item

View File

@@ -56,7 +56,7 @@ test.describe('Kindness act assignment', () => {
await expect(page.getByRole('button', { name: 'Assign Kindness Acts' })).toBeVisible()
await page.getByRole('button', { name: 'Assign Kindness Acts' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Kindness Acts' })).toBeVisible()
@@ -72,7 +72,7 @@ test.describe('Kindness act assignment', () => {
test('Select a kindness act and submit — item appears in ParentView', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Kindness Acts' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Kindness Acts' })).toBeVisible()
await page.getByText(KIND1_NAME, { exact: true }).click()
@@ -101,7 +101,7 @@ test.describe('Kindness act assignment', () => {
}) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Kindness Acts' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Kindness Acts' })).toBeVisible()
// Uncheck the previously assigned item
@@ -120,7 +120,7 @@ test.describe('Kindness act assignment', () => {
test('Cancel does not persist selection changes', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Kindness Acts' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-kindness(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Kindness Acts' })).toBeVisible()
// Check the item

View File

@@ -66,7 +66,7 @@ test.describe('Penalty assignment', () => {
await expect(page.getByRole('button', { name: 'Assign Penalties' })).toBeVisible()
await page.getByRole('button', { name: 'Assign Penalties' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Penalties' })).toBeVisible()
@@ -85,7 +85,7 @@ test.describe('Penalty assignment', () => {
}) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Penalties' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Penalties' })).toBeVisible()
// Select two penalties
@@ -128,7 +128,7 @@ test.describe('Penalty assignment', () => {
test('Uncheck all penalties and submit — items are removed from ParentView', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Penalties' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Penalties' })).toBeVisible()
// Uncheck the previously assigned penalties
@@ -153,7 +153,7 @@ test.describe('Penalty assignment', () => {
test('Cancel does not persist selection changes', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Penalties' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-penalties(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Penalties' })).toBeVisible()
// Check an item

View File

@@ -62,7 +62,7 @@ test.describe('Reward assignment', () => {
await expect(page.getByRole('button', { name: 'Assign Rewards' })).toBeVisible()
await page.getByRole('button', { name: 'Assign Rewards' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Rewards' })).toBeVisible()
@@ -81,7 +81,7 @@ test.describe('Reward assignment', () => {
}) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Rewards' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Rewards' })).toBeVisible()
// Select two rewards
@@ -124,7 +124,7 @@ test.describe('Reward assignment', () => {
test('Uncheck all rewards and submit — items are removed from ParentView', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Rewards' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Rewards' })).toBeVisible()
// Uncheck the previously assigned rewards
@@ -149,7 +149,7 @@ test.describe('Reward assignment', () => {
test('Cancel does not persist selection changes', async ({ page }) => {
await page.goto(`/parent/${childId}`)
await page.getByRole('button', { name: 'Assign Rewards' }).click()
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards$`))
await page.waitForURL(new RegExp(`/parent/${childId}/assign-rewards(\\?|$)`))
await expect(page.getByRole('heading', { name: 'Assign Rewards' })).toBeVisible()
// Check an item

View File

@@ -70,6 +70,15 @@ test.describe('Chore reset', () => {
await request.put(`/api/child/${childId}/set-tasks`, {
data: { task_ids: [helperKindId], type: 'kindness' },
})
// Add a schedule so COMPLETED status persists after trigger
await request.put(`/api/child/${childId}/task/${choreId}/schedule`, {
data: {
mode: 'interval',
interval_days: 1,
anchor_date: new Date().toISOString().slice(0, 10),
interval_has_deadline: false,
},
})
})
test.afterAll(async ({ request }) => {