const request = require('supertest'); const app = require('../../app'); const { Task } = require('../../models'); const { createTestUser } = require('../helpers/testUtils'); describe('Subtask Ordering', () => { let agent; let testUser; beforeEach(async () => { await Task.destroy({ where: {}, truncate: true }); testUser = await createTestUser(); // Create authenticated agent agent = request.agent(app); await agent.post('/api/login').send({ email: testUser.email, password: 'password123', }); }); describe('Creating subtasks with order', () => { it('should create subtasks with sequential order values', async () => { const taskData = { name: 'Parent Task', subtasks: [ { name: 'First Subtask', isNew: true }, { name: 'Second Subtask', isNew: true }, { name: 'Third Subtask', isNew: true }, { name: 'Fourth Subtask', isNew: true }, ], }; const response = await agent .post('/api/task') .send(taskData) .expect(201); // Fetch subtasks from database directly const subtasks = await Task.findAll({ where: { parent_task_id: response.body.id }, order: [['order', 'ASC']], }); expect(subtasks).toHaveLength(4); expect(subtasks[0].name).toBe('First Subtask'); expect(subtasks[0].order).toBe(1); expect(subtasks[1].name).toBe('Second Subtask'); expect(subtasks[1].order).toBe(2); expect(subtasks[2].name).toBe('Third Subtask'); expect(subtasks[2].order).toBe(3); expect(subtasks[3].name).toBe('Fourth Subtask'); expect(subtasks[3].order).toBe(4); }); it('should handle empty subtask names and maintain order', async () => { const taskData = { name: 'Parent Task', subtasks: [ { name: 'First', isNew: true }, { name: '', isNew: true }, // Should be ignored { name: 'Second', isNew: true }, { name: ' ', isNew: true }, // Should be ignored { name: 'Third', isNew: true }, ], }; const response = await agent .post('/api/task') .send(taskData) .expect(201); const subtasks = await Task.findAll({ where: { parent_task_id: response.body.id }, order: [['order', 'ASC']], }); expect(subtasks).toHaveLength(3); expect(subtasks[0].name).toBe('First'); expect(subtasks[0].order).toBe(1); expect(subtasks[1].name).toBe('Second'); expect(subtasks[1].order).toBe(2); expect(subtasks[2].name).toBe('Third'); expect(subtasks[2].order).toBe(3); }); }); describe('Retrieving subtasks in order', () => { it('should return subtasks in correct order via API', async () => { // Create parent task with subtasks const taskData = { name: 'Parent Task', subtasks: [ { name: 'Zebra', isNew: true }, { name: 'Apple', isNew: true }, { name: 'Middle', isNew: true }, ], }; const createResponse = await agent .post('/api/task') .send(taskData) .expect(201); // Get subtasks via API const response = await agent .get(`/api/task/${createResponse.body.uid}/subtasks`) .expect(200); expect(response.body).toHaveLength(3); // Should be in creation order, not alphabetical expect(response.body[0].name).toBe('Zebra'); expect(response.body[1].name).toBe('Apple'); expect(response.body[2].name).toBe('Middle'); }); it('should return subtasks in correct order when fetching parent task', async () => { const taskData = { name: 'Parent Task', subtasks: [ { name: 'Task 1', isNew: true }, { name: 'Task 2', isNew: true }, { name: 'Task 3', isNew: true }, ], }; const createResponse = await agent .post('/api/task') .send(taskData) .expect(201); // Fetch task with subtasks const response = await agent .get(`/api/task/${createResponse.body.uid}`) .expect(200); expect(response.body.subtasks).toHaveLength(3); expect(response.body.subtasks[0].name).toBe('Task 1'); expect(response.body.subtasks[1].name).toBe('Task 2'); expect(response.body.subtasks[2].name).toBe('Task 3'); }); }); describe('Updating subtask order', () => { it('should update order when subtasks are reordered', async () => { // Create task with subtasks const createResponse = await agent .post('/api/task') .send({ name: 'Parent Task', subtasks: [ { name: 'First', isNew: true }, { name: 'Second', isNew: true }, { name: 'Third', isNew: true }, ], }) .expect(201); // Fetch subtasks const subtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); // Reorder subtasks: Third, First, Second const updateData = { name: 'Parent Task', subtasks: [ { id: subtasks[2].id, name: 'Third' }, // Was 3rd, now 1st { id: subtasks[0].id, name: 'First' }, // Was 1st, now 2nd { id: subtasks[1].id, name: 'Second' }, // Was 2nd, now 3rd ], }; await agent .patch(`/api/task/${createResponse.body.uid}`) .send(updateData) .expect(200); // Verify new order const updatedSubtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); expect(updatedSubtasks[0].name).toBe('Third'); expect(updatedSubtasks[0].order).toBe(1); expect(updatedSubtasks[1].name).toBe('First'); expect(updatedSubtasks[1].order).toBe(2); expect(updatedSubtasks[2].name).toBe('Second'); expect(updatedSubtasks[2].order).toBe(3); }); it('should maintain order when adding new subtasks to existing ones', async () => { // Create task with 2 subtasks const createResponse = await agent .post('/api/task') .send({ name: 'Parent Task', subtasks: [ { name: 'First', isNew: true }, { name: 'Second', isNew: true }, ], }) .expect(201); const existingSubtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); // Add new subtasks while keeping existing ones const updateData = { name: 'Parent Task', subtasks: [ { id: existingSubtasks[0].id, name: 'First' }, { id: existingSubtasks[1].id, name: 'Second' }, { name: 'Third', isNew: true }, { name: 'Fourth', isNew: true }, ], }; await agent .patch(`/api/task/${createResponse.body.uid}`) .send(updateData) .expect(200); const allSubtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); expect(allSubtasks).toHaveLength(4); expect(allSubtasks[0].name).toBe('First'); expect(allSubtasks[0].order).toBe(1); expect(allSubtasks[1].name).toBe('Second'); expect(allSubtasks[1].order).toBe(2); expect(allSubtasks[2].name).toBe('Third'); expect(allSubtasks[2].order).toBe(3); expect(allSubtasks[3].name).toBe('Fourth'); expect(allSubtasks[3].order).toBe(4); }); it('should handle deleting and reordering subtasks simultaneously', async () => { // Create task with 4 subtasks const createResponse = await agent .post('/api/task') .send({ name: 'Parent Task', subtasks: [ { name: 'First', isNew: true }, { name: 'Second', isNew: true }, { name: 'Third', isNew: true }, { name: 'Fourth', isNew: true }, ], }) .expect(201); const subtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); // Keep only 1st and 3rd, in reverse order const updateData = { name: 'Parent Task', subtasks: [ { id: subtasks[2].id, name: 'Third' }, { id: subtasks[0].id, name: 'First' }, ], }; await agent .patch(`/api/task/${createResponse.body.uid}`) .send(updateData) .expect(200); const remainingSubtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); expect(remainingSubtasks).toHaveLength(2); expect(remainingSubtasks[0].name).toBe('Third'); expect(remainingSubtasks[0].order).toBe(1); expect(remainingSubtasks[1].name).toBe('First'); expect(remainingSubtasks[1].order).toBe(2); }); }); describe('Edge cases', () => { it('should handle many subtasks (100+) with correct ordering', async () => { const subtasks = Array.from({ length: 100 }, (_, i) => ({ name: `Subtask ${i + 1}`, isNew: true, })); const createResponse = await agent .post('/api/task') .send({ name: 'Parent Task', subtasks, }) .expect(201); const createdSubtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); expect(createdSubtasks).toHaveLength(100); createdSubtasks.forEach((subtask, index) => { expect(subtask.name).toBe(`Subtask ${index + 1}`); expect(subtask.order).toBe(index + 1); }); }); it('should handle concurrent subtask creation correctly', async () => { // Create parent task const createResponse = await agent .post('/api/task') .send({ name: 'Parent Task' }) .expect(201); // Add subtasks in multiple concurrent requests const promises = [ agent.patch(`/api/task/${createResponse.body.uid}`).send({ name: 'Parent Task', subtasks: [ { name: 'Batch 1 - Task 1', isNew: true }, { name: 'Batch 1 - Task 2', isNew: true }, ], }), // Note: This test demonstrates the limitation - concurrent updates // may result in inconsistent state. In practice, the UI prevents this. ]; await Promise.all(promises); const subtasks = await Task.findAll({ where: { parent_task_id: createResponse.body.id }, order: [['order', 'ASC']], }); // Verify all subtasks have order values expect(subtasks.length).toBeGreaterThan(0); subtasks.forEach((subtask) => { expect(subtask.order).toBeDefined(); expect(subtask.order).toBeGreaterThan(0); }); }); }); });