feat(11-01): schema, service, and tests for sort_order + reorderCandidates
- Add sortOrder REAL column to threadCandidates schema (default 0) - Add sort_order column to test helper CREATE TABLE - Add reorderCandidatesSchema to shared/schemas.ts - Add ReorderCandidates type to shared/types.ts - getThreadWithCandidates now orders candidates by sort_order ASC - createCandidate appends at max sort_order + 1000 (first = 1000) - Add reorderCandidates service function (transaction, active-only guard) - Add 5 new tests: ordering, appending, reorder success, resolved guard, missing thread
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
deleteThread,
|
||||
getAllThreads,
|
||||
getThreadWithCandidates,
|
||||
reorderCandidates,
|
||||
resolveThread,
|
||||
updateCandidate,
|
||||
updateThread,
|
||||
@@ -362,6 +363,95 @@ describe("Thread Service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sort_order ordering", () => {
|
||||
it("getThreadWithCandidates returns candidates ordered by sort_order ascending", () => {
|
||||
const thread = createThread(db, { name: "Order Test", categoryId: 1 });
|
||||
const c1 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 1",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c2 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 2",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c3 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 3",
|
||||
categoryId: 1,
|
||||
});
|
||||
|
||||
// Manually set sort_orders out of creation order using reorderCandidates
|
||||
reorderCandidates(db, thread.id, [c3.id, c1.id, c2.id]);
|
||||
|
||||
const result = getThreadWithCandidates(db, thread.id);
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.candidates[0].id).toBe(c3.id);
|
||||
expect(result?.candidates[1].id).toBe(c1.id);
|
||||
expect(result?.candidates[2].id).toBe(c2.id);
|
||||
});
|
||||
|
||||
it("createCandidate assigns sort_order = max existing sort_order + 1000", () => {
|
||||
const thread = createThread(db, { name: "Append Test", categoryId: 1 });
|
||||
|
||||
// First candidate should get sort_order 1000
|
||||
const c1 = createCandidate(db, thread.id, {
|
||||
name: "First",
|
||||
categoryId: 1,
|
||||
});
|
||||
expect(c1.sortOrder).toBe(1000);
|
||||
|
||||
// Second candidate should get sort_order 2000
|
||||
const c2 = createCandidate(db, thread.id, {
|
||||
name: "Second",
|
||||
categoryId: 1,
|
||||
});
|
||||
expect(c2.sortOrder).toBe(2000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reorderCandidates", () => {
|
||||
it("reorderCandidates updates sort_order so querying returns candidates in new order", () => {
|
||||
const thread = createThread(db, { name: "Reorder Test", categoryId: 1 });
|
||||
const c1 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 1",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c2 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 2",
|
||||
categoryId: 1,
|
||||
});
|
||||
const c3 = createCandidate(db, thread.id, {
|
||||
name: "Candidate 3",
|
||||
categoryId: 1,
|
||||
});
|
||||
|
||||
const result = reorderCandidates(db, thread.id, [c3.id, c1.id, c2.id]);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const fetched = getThreadWithCandidates(db, thread.id);
|
||||
expect(fetched?.candidates[0].id).toBe(c3.id);
|
||||
expect(fetched?.candidates[1].id).toBe(c1.id);
|
||||
expect(fetched?.candidates[2].id).toBe(c2.id);
|
||||
});
|
||||
|
||||
it("returns { success: false, error } when thread status is 'resolved'", () => {
|
||||
const thread = createThread(db, { name: "Resolved Thread", categoryId: 1 });
|
||||
const candidate = createCandidate(db, thread.id, {
|
||||
name: "Winner",
|
||||
categoryId: 1,
|
||||
});
|
||||
resolveThread(db, thread.id, candidate.id);
|
||||
|
||||
const result = reorderCandidates(db, thread.id, [candidate.id]);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it("returns { success: false } when thread does not exist", () => {
|
||||
const result = reorderCandidates(db, 9999, [1, 2]);
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveThread", () => {
|
||||
it("atomically creates collection item from candidate data and archives thread", () => {
|
||||
const thread = createThread(db, { name: "Tent Decision", categoryId: 1 });
|
||||
|
||||
Reference in New Issue
Block a user