feat: Implement drag-and-drop reordering for routine items and add corresponding E2E tests
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m41s

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-19 19:30:44 -04:00
parent ad8a8bf867
commit a6944ad59c
7 changed files with 324 additions and 83 deletions

View File

@@ -2,20 +2,20 @@
"cookies": [
{
"name": "refresh_token",
"value": "QCuLIgmPV2vnDfzmgvptt-mZAW1jh8UML_wwAee4OTQ",
"value": "GyPtMIGYCphOLBJzZ6jG87S9PKImCOhrVEvuVL0-Wu8",
"domain": "localhost",
"path": "/api/auth",
"expires": 1786912678.702645,
"expires": 1786993807.67933,
"httpOnly": true,
"secure": true,
"sameSite": "Strict"
},
{
"name": "access_token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJlM2VmNGE2OC0zZWI3LTQ2MjQtYTI0Mi0yNmY1OTgyZDIwOWEiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkxNDc0Nzh9.P7CDplAG3MBXgtEpuYFtp6gOV5pi-6QeAYRqmE-Dkys",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJjNWUzMTkwYi1hZWIzLTRlMTEtYmFiNS1hNjBkOTM5NmEyN2QiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzkyMjg2MDd9.f7dGIujexK9ffYWPM5ExUbL_UPRg0ULo778AR2J_yf0",
"domain": "localhost",
"path": "/",
"expires": 1779147478.701971,
"expires": 1779228607.679281,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
@@ -27,11 +27,11 @@
"localStorage": [
{
"name": "authSyncEvent",
"value": "{\"type\":\"logout\",\"at\":1779136678495}"
"value": "{\"type\":\"logout\",\"at\":1779217807539}"
},
{
"name": "parentAuth",
"value": "{\"expiresAt\":1779309478870}"
"value": "{\"expiresAt\":1779390607821}"
}
]
}

View File

@@ -0,0 +1,77 @@
import { test, expect, type APIRequestContext } from '@playwright/test'
const ROUTINE_NAME = 'ReorderTestRoutine'
const ROUTINE_POINTS = 10
const ITEM_A = 'Alpha Task'
const ITEM_B = 'Beta Task'
const ITEM_C = 'Gamma Task'
async function createRoutine(
request: APIRequestContext,
name: string,
points: number,
): Promise<string> {
const pre = await request.get('/api/routine/list')
for (const r of (await pre.json()).routines ?? []) {
if (r.name === name) await request.delete(`/api/routine/${r.id}`)
}
const res = await request.put('/api/routine/add', { data: { name, points } })
return (await res.json()).routine?.id ?? ''
}
test.describe('Routine item drag-to-reorder', () => {
test.describe.configure({ mode: 'serial' })
let routineId = ''
test.beforeAll(async ({ request }) => {
routineId = await createRoutine(request, ROUTINE_NAME, ROUTINE_POINTS)
await request.put(`/api/routine/${routineId}/item/add`, { data: { name: ITEM_A, order: 0 } })
await request.put(`/api/routine/${routineId}/item/add`, { data: { name: ITEM_B, order: 1 } })
await request.put(`/api/routine/${routineId}/item/add`, { data: { name: ITEM_C, order: 2 } })
})
test.afterAll(async ({ request }) => {
if (routineId) await request.delete(`/api/routine/${routineId}`)
})
test('items load in correct initial order', async ({ page }) => {
await page.goto(`/parent/tasks/routines/${routineId}/edit`)
await page.locator('.item-row').first().waitFor({ state: 'visible' })
const names = await page.locator('.item-name').allTextContents()
expect(names).toEqual([ITEM_A, ITEM_B, ITEM_C])
})
test('dragging first item to last position reorders the list in the UI', async ({ page }) => {
await page.goto(`/parent/tasks/routines/${routineId}/edit`)
await page.locator('.item-row').first().waitFor({ state: 'visible' })
await page.locator('.item-row').nth(0).dragTo(page.locator('.item-row').nth(2))
const names = await page.locator('.item-name').allTextContents()
expect(names).toEqual([ITEM_B, ITEM_C, ITEM_A])
})
test('reordered item order persists after saving', async ({ page }) => {
await page.goto(`/parent/tasks/routines/${routineId}/edit`)
await page.locator('.item-row').first().waitFor({ state: 'visible' })
// Drag first to last: [A, B, C] → [B, C, A]
await page.locator('.item-row').nth(0).dragTo(page.locator('.item-row').nth(2))
// Confirm the drag updated the DOM before saving
const afterDrag = await page.locator('.item-name').allTextContents()
expect(afterDrag).toEqual([ITEM_B, ITEM_C, ITEM_A])
await page.getByRole('button', { name: 'Save' }).click()
await page.waitForURL(/\/parent\/tasks\/routines$/, { timeout: 5000 })
// Reload edit view — should now show the saved order
await page.goto(`/parent/tasks/routines/${routineId}/edit`)
await page.locator('.item-row').first().waitFor({ state: 'visible' })
const names = await page.locator('.item-name').allTextContents()
expect(names).toEqual([ITEM_B, ITEM_C, ITEM_A])
})
})