Skip to content

Commit

Permalink
FEATURE : Add pagination in tasks page (#697)
Browse files Browse the repository at this point in the history
* FEATURE: add pagination feature for fetch tasks

* FIX: Fix type error

* FIX: Fix type error

* FIX: Fix next and prev task fetching error

* FIX: bug  from selection of tabs

* REFACTOR: add loader

* REFACTOR: created separate task loader components

* FIX: fix test and remove loader

* REFACTOR: created separate render task list components

* TEST: add test and refactor component

* REFACTOR: created separate types for taskApi

* REFACTOR: created separate components for pagination button

* REFACTOR: remove uncessary ternary operator
  • Loading branch information
sahsisunny authored Jul 14, 2023
1 parent 4cac085 commit bc04ca3
Show file tree
Hide file tree
Showing 13 changed files with 464 additions and 53 deletions.
111 changes: 110 additions & 1 deletion __mocks__/db/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,113 @@ const tasks: task[] = Array.from({ length: 10 }).map((_, index) => ({
id: TASK.id + index,
}));

export { tasks, TASK };
export { tasks, TASK };

const PAGINATED_TASKS = {
tasks: [
{
id: 'yVC1KqYuUTZdkUFqF9NY',
percentCompleted: 0,
isNoteworthy: false,
createdBy: 'sahsi',
lossRate: {
dinero: 100,
neelam: 0
},
assignee: false,
type: 'feature',
priority: 'HIGH',
completionAward: {
dinero: 1000,
neelam: 0
},
status: 'AVAILABLE',
title: 'Design and develop an online booking system',
dependsOn: []
},
{
id: 'ei2hlGV2BSJ3bRDAbkty',
percentCompleted: 0,
isNoteworthy: false,
createdBy: 'sahsi',
lossRate: {
dinero: 100,
neelam: 0
},
assignee: false,
type: 'feature',
priority: 'HIGH',
completionAward: {
dinero: 1000,
neelam: 0
},
status: 'AVAILABLE',
title: 'Develop a mobile game using Unity',
dependsOn: []
},
{
id: 'tCLdKbDDW0Bl9NVv3bRa',
percentCompleted: 0,
isNoteworthy: false,
createdBy: 'sahsi',
lossRate: {
dinero: 100,
neelam: 0
},
assignee: false,
type: 'feature',
priority: 'HIGH',
completionAward: {
dinero: 1000,
neelam: 0
},
status: 'AVAILABLE',
title: 'Implement a content management system (CMS)',
dependsOn: []
},
{
id: '8ygXLYcsCORg2JEHiSgr',
percentCompleted: 0,
isNoteworthy: false,
createdBy: 'sahsi',
lossRate: {
dinero: 100,
neelam: 0
},
assignee: false,
type: 'feature',
priority: 'HIGH',
completionAward: {
dinero: 1000,
neelam: 0
},
status: 'AVAILABLE',
title: 'Implement a search functionality for a product catalog',
dependsOn: []
},
{
id: 'P86Y1hVrS0zR5ZcVPwLZ',
percentCompleted: 0,
isNoteworthy: false,
createdBy: 'sahsi',
lossRate: {
dinero: 100,
neelam: 0
},
assignee: false,
type: 'feature',
priority: 'HIGH',
completionAward: {
dinero: 1000,
neelam: 0
},
status: 'AVAILABLE',
title: 'Implement user authentication and authorization',
dependsOn: []
}
],
prev: '/tasks?status=AVAILABLE&dev=true&size=5&prev=ARn1G8IxUt1zrfMdTyfn',
next: '/tasks?status=AVAILABLE&dev=true&size=5&next=aZAxHchprtpuMGsTOqqo'
};

export { PAGINATED_TASKS };
3 changes: 2 additions & 1 deletion __mocks__/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import taskHandlers from './handlers/tasks.handler';
import taskHandlers, { paginatedTasksHandler } from './handlers/tasks.handler';
import selfHandler from './handlers/self.handler';
import userStatusHandler from './handlers/users-status.handler';
import tagsHandler from './handlers/tags.handler';
Expand All @@ -11,6 +11,7 @@ import issuesHandler from './handlers/issues.handler';
import { prsHandler } from './handlers/pull-requests.handler';

const handlers = [
...paginatedTasksHandler,
...taskHandlers,
...selfHandler,
...userStatusHandler,
Expand Down
31 changes: 22 additions & 9 deletions __mocks__/handlers/tasks.handler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { tasks } from '../db/tasks';
import { tasks, PAGINATED_TASKS } from '../db/tasks';
import { rest } from 'msw';
const URL = process.env.NEXT_PUBLIC_BASE_URL;

Expand Down Expand Up @@ -42,6 +42,7 @@ export const failedAddNewTaskResponse = {
error: 'Bad Request',
message: '"title" is required',
};

export const failedAddNewTaskHandler = rest.post(
`${URL}/tasks`,
(_, res, ctx) => {
Expand All @@ -54,6 +55,7 @@ export const failedUpdateTaskResponse = {
error: 'Not Found',
message: 'Task not found',
};

export const failedUpdateTaskHandler = rest.patch(
`${URL}/tasks/:taskId`,
(_, res, ctx) => {
Expand All @@ -62,16 +64,27 @@ export const failedUpdateTaskHandler = rest.patch(
);

export const noTasksFoundResponse = {
message: 'No Tasks Found',
tasks: []
message: 'No Tasks Found',
tasks: []
};

export const noTasksFoundHandler = rest.get(
`${URL}/tasks`,
(_, res, ctx) => {
return res(ctx.status(200), ctx.json(noTasksFoundResponse)
);
}
`${URL}/tasks`,
(_, res, ctx) => {
return res(ctx.status(200), ctx.json(noTasksFoundResponse)
);
}
);


export default taskHandlers;

export const paginatedTasksHandler = [
rest.get(`${URL}/tasks`, (_, res, ctx) => {
return res(
ctx.status(200),
ctx.json(
PAGINATED_TASKS
)
);
}),
];
36 changes: 32 additions & 4 deletions __tests__/Unit/Components/Tasks/TasksContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import { fireEvent, screen } from '@testing-library/react';
import { renderWithRouter } from '@/test_utils/createMockRouter';
import { Provider } from 'react-redux';
import { store } from '@/app/store';
import {
NO_TASKS_FOUND_MESSAGE,
TASKS_FETCH_ERROR_MESSAGE,
} from '@/constants/messages';
import { NO_TASKS_FOUND_MESSAGE } from '@/constants/messages';
import { noTasksFoundHandler } from '../../../../__mocks__//handlers/tasks.handler';

const server = setupServer(...handlers);
Expand Down Expand Up @@ -43,6 +40,7 @@ describe('tasks content', () => {
});
fireEvent.click(assignedButton);
expect(assignedButton).toHaveClass('active');
await screen.findByText(NO_TASKS_FOUND_MESSAGE);
expect(screen.getByText(NO_TASKS_FOUND_MESSAGE)).toBeInTheDocument();
});

Expand All @@ -56,4 +54,34 @@ describe('tasks content', () => {
const errorMessage = await findByText(NO_TASKS_FOUND_MESSAGE);
expect(errorMessage).toBeInTheDocument();
});

test('display tasks when dev is true', async () => {
const { findByText } = renderWithRouter(
<Provider store={store()}>
<TasksContent />
</Provider>,
{ query: { dev: 'true' } }
);
const task = await findByText(
'Design and develop an online booking system'
);
expect(task).toBeInTheDocument();
});
test('display tasks when dev is false', async () => {
const { findByText } = renderWithRouter(
<Provider store={store()}>
<TasksContent />
</Provider>,
{ query: { dev: 'false' } }
);
await screen.findByTestId('tabs');
const availableButton = screen.getByRole('button', {
name: /UNASSINGED/i,
});
fireEvent.click(availableButton);
const task = await findByText(
'Design and develop an online booking system'
);
expect(task).toBeInTheDocument();
});
});
51 changes: 51 additions & 0 deletions __tests__/Unit/hooks/tasksApi.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,57 @@ describe('useGetAllTasksQuery()', () => {

await act(() => waitForNextUpdate());

const nextResponse = result.current;
const tasksData = nextResponse.data;
expect(tasksData).not.toBeUndefined();
expect(nextResponse.isLoading).toBe(false);
expect(nextResponse.isSuccess).toBe(true);
});
test('returns next page of tasks', async () => {
const dev = true;
const { result, waitForNextUpdate } = renderHook(
() =>
useGetAllTasksQuery({
dev: dev as boolean,
nextPage:
'/tasks?status=AVAILABLE&dev=true&size=5&next=yVC1KqYuUTZdkUFqF9NY',
}),
{
wrapper: Wrapper,
}
);
const initialResponse = result.current;
expect(initialResponse.data).toBeUndefined();
expect(initialResponse.isLoading).toBe(true);

await act(() => waitForNextUpdate());

const nextResponse = result.current;
const tasksData = nextResponse.data;
expect(tasksData).not.toBeUndefined();
expect(nextResponse.isLoading).toBe(false);
expect(nextResponse.isSuccess).toBe(true);
});

test('returns prev page of tasks', async () => {
const dev = true;
const { result, waitForNextUpdate } = renderHook(
() =>
useGetAllTasksQuery({
dev: dev as boolean,
prevPage:
'/tasks?status=AVAILABLE&dev=true&size=5&prev=ARn1G8IxUt1zrfMdTyfn',
}),
{
wrapper: Wrapper,
}
);
const initialResponse = result.current;
expect(initialResponse.data).toBeUndefined();
expect(initialResponse.isLoading).toBe(true);

await act(() => waitForNextUpdate());

const nextResponse = result.current;
const tasksData = nextResponse.data;
expect(tasksData).not.toBeUndefined();
Expand Down
53 changes: 30 additions & 23 deletions src/app/services/tasksApi.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
import task, { updateTaskDetails } from '@/interfaces/task.type';
import task, {
updateTaskDetails,
TasksResponseType,
GetAllTaskParamType,
} from '@/interfaces/task.type';
import { api } from './api';
import { MINE_TASKS_URL, TASKS_URL } from '@/constants/url';

type TasksQueryResponse = { message: string; tasks: task[] };
type TasksCreateMutationResponse = { message: string; task: task };
type TaskRequestPayload = { task: updateTaskDetails; id: string };

export const tasksApi = api.injectEndpoints({
endpoints: (builder) => ({
getAllTasks: builder.query<
TasksQueryResponse['tasks'],
{ dev: boolean; status?: string }
>({
query: ({ dev, status }) =>
dev ? `/tasks?status=${status}&dev=true` : '/tasks',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({
type: 'Tasks' as const,
id,
})),
{ type: 'Tasks' },
]
: ['Tasks'],
transformResponse: (response: TasksQueryResponse) => {
return response?.tasks?.sort(
(a: task, b: task) => +a.endsOn - +b.endsOn
);
getAllTasks: builder.query<TasksResponseType, GetAllTaskParamType>({
query: ({ dev, status, nextPage, prevPage }) => {
let url = dev ? `/tasks?status=${status}&dev=true` : '/tasks';

if (nextPage) {
url = nextPage;
}

if (prevPage) {
url = prevPage;
}

return { url };
},
providesTags: ['Tasks'],

transformResponse: (response: TasksResponseType) => {
return {
tasks: response.tasks?.sort(
(a: task, b: task) => +a.endsOn - +b.endsOn
),
next: response.next,
prev: response.prev,
};
},
}),

getMineTasks: builder.query<TasksQueryResponse['tasks'], void>({
getMineTasks: builder.query<TasksResponseType, void>({
query: () => MINE_TASKS_URL,
providesTags: ['Mine_Tasks'],
}),
Expand Down
Loading

0 comments on commit bc04ca3

Please sign in to comment.