Add drag & drop reordering of trips on the dashboard
- trip_members.sort_order (per-user, migrated) drives GET /api/trips order - PATCH /api/trips/:id/order updates only the caller's membership row - Trip cards get a drag handle reusing the generalized row-reorder helper; clicking the card still navigates
This commit is contained in:
@@ -172,6 +172,61 @@ test('unknown trip returns 404', async () => {
|
||||
assert.equal(res.body.error, 'not found');
|
||||
});
|
||||
|
||||
test('PATCH /api/trips/:id/order reorders the dashboard list for the caller', async () => {
|
||||
const { agent } = await createAccount();
|
||||
const a = (await agent.post('/api/trips').send({ name: 'A', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
const b = (await agent.post('/api/trips').send({ name: 'B', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
const c = (await agent.post('/api/trips').send({ name: 'C', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
|
||||
// Default order is newest first: C, B, A.
|
||||
const initial = await agent.get('/api/trips');
|
||||
assert.deepEqual(initial.body.trips.map((t) => t.id), [c.id, b.id, a.id]);
|
||||
assert.ok(initial.body.trips.every((t) => t.sort_order === 0));
|
||||
|
||||
const setA = await agent.patch(`/api/trips/${a.id}/order`).send({ sort_order: 0 });
|
||||
assert.equal(setA.status, 200);
|
||||
assert.deepEqual(setA.body, { sort_order: 0 });
|
||||
await agent.patch(`/api/trips/${b.id}/order`).send({ sort_order: 2 });
|
||||
await agent.patch(`/api/trips/${c.id}/order`).send({ sort_order: 1 });
|
||||
|
||||
const reordered = await agent.get('/api/trips');
|
||||
assert.deepEqual(reordered.body.trips.map((t) => t.id), [a.id, c.id, b.id]);
|
||||
});
|
||||
|
||||
test('trip order is per-user', async () => {
|
||||
const owner = await createAccount();
|
||||
const guest = await createAccount();
|
||||
const t1 = (await owner.agent.post('/api/trips').send({ name: 'T1', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
const t2 = (await owner.agent.post('/api/trips').send({ name: 'T2', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
await guest.agent.post('/api/trips/join').send({ code: t1.join_code });
|
||||
await guest.agent.post('/api/trips/join').send({ code: t2.join_code });
|
||||
|
||||
// Owner reorders their own list; guest's list is unaffected.
|
||||
const guestBefore = (await guest.agent.get('/api/trips')).body.trips.map((t) => t.id);
|
||||
await owner.agent.patch(`/api/trips/${t1.id}/order`).send({ sort_order: 5 });
|
||||
const guestAfter = (await guest.agent.get('/api/trips')).body.trips.map((t) => t.id);
|
||||
assert.deepEqual(guestAfter, guestBefore);
|
||||
|
||||
const ownerList = (await owner.agent.get('/api/trips')).body.trips;
|
||||
assert.equal(ownerList.find((t) => t.id === t1.id).sort_order, 5);
|
||||
});
|
||||
|
||||
test('order endpoint validation: non-integer 400, non-member 404', async () => {
|
||||
const owner = await createAccount();
|
||||
const outsider = await createAccount();
|
||||
const trip = (await owner.agent.post('/api/trips').send({ name: 'T', start_date: '2026-08-01', end_date: '2026-08-02' })).body.trip;
|
||||
|
||||
const badBody = await owner.agent.patch(`/api/trips/${trip.id}/order`).send({ sort_order: 'first' });
|
||||
assert.equal(badBody.status, 400);
|
||||
assert.equal(badBody.body.error, 'sort_order must be an integer');
|
||||
|
||||
const missing = await owner.agent.patch(`/api/trips/${trip.id}/order`).send({});
|
||||
assert.equal(missing.status, 400);
|
||||
|
||||
const notMember = await outsider.agent.patch(`/api/trips/${trip.id}/order`).send({ sort_order: 1 });
|
||||
assert.equal(notMember.status, 404);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Membership via join code
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user