feat: add comma-separated status filtering to list-tasks
- supports multiple statuses like 'blocked,deferred' with comprehensive test coverage and backward compatibility - also adjusts biome.json to stop bitching about templating.
This commit is contained in:
@@ -109,6 +109,14 @@ const sampleTasks = {
|
||||
status: 'cancelled',
|
||||
dependencies: [2, 3],
|
||||
priority: 'low'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'Code Review',
|
||||
description: 'Review code for quality and standards',
|
||||
status: 'review',
|
||||
dependencies: [3],
|
||||
priority: 'medium'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -154,7 +162,8 @@ describe('listTasks', () => {
|
||||
expect.objectContaining({ id: 1 }),
|
||||
expect.objectContaining({ id: 2 }),
|
||||
expect.objectContaining({ id: 3 }),
|
||||
expect.objectContaining({ id: 4 })
|
||||
expect.objectContaining({ id: 4 }),
|
||||
expect.objectContaining({ id: 5 })
|
||||
])
|
||||
})
|
||||
);
|
||||
@@ -174,6 +183,7 @@ describe('listTasks', () => {
|
||||
// Verify only pending tasks are returned
|
||||
expect(result.tasks).toHaveLength(1);
|
||||
expect(result.tasks[0].status).toBe('pending');
|
||||
expect(result.tasks[0].id).toBe(2);
|
||||
});
|
||||
|
||||
test('should filter tasks by done status', async () => {
|
||||
@@ -190,6 +200,21 @@ describe('listTasks', () => {
|
||||
expect(result.tasks[0].status).toBe('done');
|
||||
});
|
||||
|
||||
test('should filter tasks by review status', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'review';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Verify only review tasks are returned
|
||||
expect(result.tasks).toHaveLength(1);
|
||||
expect(result.tasks[0].status).toBe('review');
|
||||
expect(result.tasks[0].id).toBe(5);
|
||||
});
|
||||
|
||||
test('should include subtasks when withSubtasks option is true', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
@@ -320,13 +345,203 @@ describe('listTasks', () => {
|
||||
tasks: expect.any(Array),
|
||||
filter: 'all',
|
||||
stats: expect.objectContaining({
|
||||
total: 4,
|
||||
total: 5,
|
||||
completed: expect.any(Number),
|
||||
inProgress: expect.any(Number),
|
||||
pending: expect.any(Number)
|
||||
})
|
||||
})
|
||||
);
|
||||
expect(result.tasks).toHaveLength(4);
|
||||
expect(result.tasks).toHaveLength(5);
|
||||
});
|
||||
|
||||
// Tests for comma-separated status filtering
|
||||
describe('Comma-separated status filtering', () => {
|
||||
test('should filter tasks by multiple statuses separated by commas', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done,pending';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
expect(readJSON).toHaveBeenCalledWith(tasksPath);
|
||||
|
||||
// Should return tasks with 'done' or 'pending' status
|
||||
expect(result.tasks).toHaveLength(2);
|
||||
expect(result.tasks.map((task) => task.status)).toEqual(
|
||||
expect.arrayContaining(['done', 'pending'])
|
||||
);
|
||||
|
||||
// Verify specific tasks
|
||||
const taskIds = result.tasks.map((task) => task.id);
|
||||
expect(taskIds).toContain(1); // done task
|
||||
expect(taskIds).toContain(2); // pending task
|
||||
});
|
||||
|
||||
test('should filter tasks by three or more statuses', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done,pending,in-progress';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return tasks with 'done', 'pending', or 'in-progress' status
|
||||
expect(result.tasks).toHaveLength(3);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(
|
||||
expect.arrayContaining(['done', 'pending', 'in-progress'])
|
||||
);
|
||||
|
||||
// Verify all matching tasks are included
|
||||
const taskIds = result.tasks.map((task) => task.id);
|
||||
expect(taskIds).toContain(1); // done
|
||||
expect(taskIds).toContain(2); // pending
|
||||
expect(taskIds).toContain(3); // in-progress
|
||||
expect(taskIds).not.toContain(4); // cancelled - should not be included
|
||||
});
|
||||
|
||||
test('should handle spaces around commas in status filter', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done, pending , in-progress';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should trim spaces and work correctly
|
||||
expect(result.tasks).toHaveLength(3);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(
|
||||
expect.arrayContaining(['done', 'pending', 'in-progress'])
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle empty status values in comma-separated list', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done,,pending,';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should ignore empty values and work with valid ones
|
||||
expect(result.tasks).toHaveLength(2);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(expect.arrayContaining(['done', 'pending']));
|
||||
});
|
||||
|
||||
test('should handle case-insensitive matching for comma-separated statuses', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'DONE,Pending,IN-PROGRESS';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should match case-insensitively
|
||||
expect(result.tasks).toHaveLength(3);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(
|
||||
expect.arrayContaining(['done', 'pending', 'in-progress'])
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty array when no tasks match comma-separated statuses', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'blocked,deferred';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return empty array as no tasks have these statuses
|
||||
expect(result.tasks).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should work with single status when using comma syntax', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'pending,';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should work the same as single status filter
|
||||
expect(result.tasks).toHaveLength(1);
|
||||
expect(result.tasks[0].status).toBe('pending');
|
||||
});
|
||||
|
||||
test('should set correct filter value in response for comma-separated statuses', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done,pending';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return the original filter string
|
||||
expect(result.filter).toBe('done,pending');
|
||||
});
|
||||
|
||||
test('should handle all statuses filter with comma syntax', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'all';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return all tasks when filter is 'all'
|
||||
expect(result.tasks).toHaveLength(5);
|
||||
expect(result.filter).toBe('all');
|
||||
});
|
||||
|
||||
test('should handle mixed existing and non-existing statuses', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'done,nonexistent,pending';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return only tasks with existing statuses
|
||||
expect(result.tasks).toHaveLength(2);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(expect.arrayContaining(['done', 'pending']));
|
||||
});
|
||||
|
||||
test('should filter by review status in comma-separated list', async () => {
|
||||
// Arrange
|
||||
const tasksPath = 'tasks/tasks.json';
|
||||
const statusFilter = 'review,cancelled';
|
||||
|
||||
// Act
|
||||
const result = listTasks(tasksPath, statusFilter, null, false, 'json');
|
||||
|
||||
// Assert
|
||||
// Should return tasks with 'review' or 'cancelled' status
|
||||
expect(result.tasks).toHaveLength(2);
|
||||
const statusValues = result.tasks.map((task) => task.status);
|
||||
expect(statusValues).toEqual(
|
||||
expect.arrayContaining(['review', 'cancelled'])
|
||||
);
|
||||
|
||||
// Verify specific tasks
|
||||
const taskIds = result.tasks.map((task) => task.id);
|
||||
expect(taskIds).toContain(4); // cancelled task
|
||||
expect(taskIds).toContain(5); // review task
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user