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
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:
@@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user