Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filter dropdown and change task card style in mine page #1264

Merged
merged 17 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 107 additions & 2 deletions __tests__/Unit/pages/Mine/Mine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
mineTasksErrorHandler,
mineTasksNoDataFoundHandler,
} from '../../../../__mocks__/handlers/tasks.handler';
import userEvent from '@testing-library/user-event';

const server = setupServer(...handlers);

Expand Down Expand Up @@ -42,12 +43,39 @@ describe('Mine Page', () => {
);
});

it('should render mine tasks', async () => {
it('should render no tasks found state when dev is enabled', async () => {
const { getByText } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine' }
{ route: '/mine', query: { dev: 'true' } }
);
await waitFor(() =>
expect(getByText(/no tasks found/i)).toBeInTheDocument()
);
});

it('should render shimmer cards', async () => {
const { getAllByTestId } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

await waitFor(() =>
expect(
getAllByTestId(/shimmer-card/i).length
).toBeGreaterThanOrEqual(1)
);
});

it('should render old UI component for mine tasks when dev is disabled', async () => {
const { getByText } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'false' } }
);
await waitFor(() =>
expect(
Expand All @@ -58,6 +86,83 @@ describe('Mine Page', () => {
);
});

it('should render filter dropdown when dev is enabled', async () => {
const { getByText } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

await waitFor(() => expect(getByText(/Filter/i)).toBeInTheDocument());
});

it('should render search input when dev is enabled', async () => {
const { getByTestId } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

await waitFor(() =>
expect(getByTestId(/pill-input-wrapper/i)).toBeInTheDocument()
);
});

it('should render new UI component for mine tasks when dev is enabled', async () => {
const { getAllByTestId } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

await waitFor(() =>
expect(getAllByTestId(/task-card/i).length).toBeGreaterThanOrEqual(
1
)
);
});

it('should filter tasks based on search input when dev is enabled', async () => {
const { findByText, getAllByText, findByTestId } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

const searchInput = await findByTestId('search-input');
userEvent.type(searchInput, 'status:verified');
const tag = await findByText('status: verified');
userEvent.click(tag);

await waitFor(() => {
expect(getAllByText('Verified').length).toEqual(2);
});
});

it('should filter tasks based on filter dropdown select when dev is enabled', async () => {
const { findByText, getAllByText } = renderWithRouter(
<Provider store={store()}>
<Mine />
</Provider>,
{ route: '/mine', query: { dev: 'true' } }
);

const dropdown = await findByText('Filter');
userEvent.click(dropdown);
const tab = await findByText('IN PROGRESS');
userEvent.click(tab);

await waitFor(() => {
expect(getAllByText('In Progress', { exact: true }).length).toEqual(
1
);
});
});

it('should render error state', async () => {
server.use(mineTasksErrorHandler);
const { getByText } = renderWithRouter(
Expand Down
79 changes: 79 additions & 0 deletions __tests__/Unit/utils/getFilteredTasks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { DONE, VERIFIED } from '@/constants/task-status';
import task, { Tab } from '@/interfaces/task.type';
import getFilteredTasks from '@/utils/getFilteredTasks';

let TASK: task;
let VERIFIED_TASKS: task[];
let TASKS_HAVING_TITLE_DONE: task[];
let ALL_TASKS: task[];

beforeAll(() => {
TASK = {
id: 'firestoreDocumentId123',
lossRate: {
dinero: 10,
neelam: 5,
},
links: ['https://realdevsquad.com/learn-site'],
completionAward: {
dinero: 110,
neelam: 10,
},
dependsOn: [],
assignee: 'shmbajaj',
startedOn: '1618790400',
isNoteworthy: true,
title: 'Testing and Determinsitic State',
purpose: 'string',
percentCompleted: 0,
endsOn: 1618790400,
status: 'progress',
featureUrl: 'progress',
type: 'feature',
createdBy: 'shmbajaj',
priority: 'TBD',
};
pankajjs marked this conversation as resolved.
Show resolved Hide resolved
VERIFIED_TASKS = [
{ ...TASK, status: VERIFIED, id: TASK.id + 2 },
{ ...TASK, status: VERIFIED, id: TASK.id + 3 },
];
TASKS_HAVING_TITLE_DONE = [
{ ...TASK, id: TASK.id + 1, title: DONE },
{ ...TASK, title: DONE, status: DONE, id: TASK.id + 4 },
];
ALL_TASKS = [...TASKS_HAVING_TITLE_DONE, ...VERIFIED_TASKS];
});

describe('Unit | Util | Get Filtered Tasks', () => {
test('should return an empty list', () => {
expect(getFilteredTasks([], Tab.ALL, '')).toStrictEqual([]);
});

test('should return all tasks', () => {
expect(getFilteredTasks(ALL_TASKS, Tab.ALL, '')).toStrictEqual(
ALL_TASKS
);
});

test('should return all tasks with verified status', () => {
expect(getFilteredTasks(ALL_TASKS, Tab.VERIFIED, '')).toStrictEqual(
VERIFIED_TASKS
);
});

test('should return all tasks with `verified` status and title `Testing and Determinsitic State`', () => {
expect(
getFilteredTasks(
ALL_TASKS,
Tab.VERIFIED,
'Testing and Determinsitic State'
)
).toStrictEqual(VERIFIED_TASKS);
});

test('should return all tasks with title `DONE`', () => {
expect(getFilteredTasks(ALL_TASKS, Tab.ALL, DONE)).toStrictEqual(
TASKS_HAVING_TITLE_DONE
);
});
});
20 changes: 20 additions & 0 deletions __tests__/Unit/utils/getInputValueFromTaskField.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Tab } from '@/interfaces/task.type';
import getInputValueFromTaskField from '@/utils/getInputValueFromTaskField';

describe('Unit | Util | Get Input Value From Task Field', () => {
test('should return proper value for `all` tab', () => {
expect(getInputValueFromTaskField(Tab.ALL, '')).toBe('status:all');
});

test('should return proper value for `assigned` tab and `done` title', () => {
expect(getInputValueFromTaskField(Tab.ASSIGNED, 'done')).toBe(
'status:assigned done'
);
});

test('should return proper value for `all` tab and `done` title', () => {
expect(getInputValueFromTaskField(Tab.ALL, 'done')).toBe(
'status:all done'
);
});
});
2 changes: 1 addition & 1 deletion src/components/Loaders/cardShimmer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import styles from './cardShimmer.module.scss';

const CardShimmer = () => (
<div className={`${styles.card} ${styles.br}`}>
<div className={`${styles.card} ${styles.br}`} data-testid="shimmer-card">
<div className={`${styles.title} ${styles.br} ${styles.animate}`} />
<div className={`${styles.comment} ${styles.br} ${styles.animate}`} />
<div className={`${styles.comment} ${styles.br} ${styles.animate}`} />
Expand Down
77 changes: 73 additions & 4 deletions src/pages/mine/index.tsx
pankajjs marked this conversation as resolved.
Show resolved Hide resolved
pankajjs marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,14 +1,77 @@
import { FC } from 'react';
import { FC, useEffect, useState } from 'react';
import Head from '@/components/head';
import Layout from '@/components/Layout';
import Card from '@/components/tasks/card';
import styles from '@/styles/tasks.module.scss';
import task from '@/interfaces/task.type';
import task, { Tab } from '@/interfaces/task.type';
import { LOGIN_URL } from '@/constants/url';
import { NO_TASKS_FOUND_MESSAGE } from '@/constants/messages';
import useAuthenticated from '@/hooks/useAuthenticated';
import { useGetMineTasksQuery } from '@/app/services/tasksApi';
import { Loader } from '@/components/tasks/card/Loader';
import TaskSearch from '@/components/tasks/TaskSearch/TaskSearch';
import { extractQueryParams } from '@/utils/taskQueryParams';
import { getActiveTab } from '@/utils/getActiveTab';
import TaskList from '@/components/tasks/TaskList/TaskList';
import getInputValueFromTaskField from '@/utils/getInputValueFromTaskField';
import getFilteredTasks from '@/utils/getFilteredTasks';
import Card from '@/components/tasks/card';
import { useRouter } from 'next/router';
import CardShimmer from '@/components/Loaders/cardShimmer';

const ContentDev = () => {
const [filteredTasks, setFilteredTasks] = useState<task[] | undefined>();
const [selectedTab, setSelectedTab] = useState<Tab>(Tab.ALL);
const [title, setTitle] = useState<string>('');

const inputValue = getInputValueFromTaskField(selectedTab, title);

const { data: tasks, error, isLoading } = useGetMineTasksQuery();

const searchTasks = (searchString?: string) => {
if (searchString && tasks) {
const { status, title } = extractQueryParams(searchString);
const tab = getActiveTab(status);
setFilteredTasks(getFilteredTasks(tasks, tab, title));
setSelectedTab(tab);
setTitle(title);
}
};

useEffect(() => {
setFilteredTasks(tasks);
}, [tasks]);

if (isLoading)
return (
<div className={styles.tasksContainer}>
{[...Array(5)].map((n: number, index) => (
<CardShimmer key={index} />
))}
</div>
);

if (error) return <p>Something went wrong! Please contact admin</p>;
if (!tasks) return <p>{NO_TASKS_FOUND_MESSAGE}</p>;
if (tasks.length === 0) return <p>{NO_TASKS_FOUND_MESSAGE}</p>;

return (
<div className={styles.tasksContainer}>
<TaskSearch
onFilterDropdownSelect={(selectedTab: Tab) => {
searchTasks(getInputValueFromTaskField(selectedTab, title));
}}
filterDropdownActiveTab={selectedTab}
inputValue={inputValue}
onClickSearchButton={searchTasks}
/>
{!filteredTasks || filteredTasks.length === 0 ? (
<p>{NO_TASKS_FOUND_MESSAGE}</p>
) : (
<TaskList tasks={filteredTasks} />
)}
</div>
);
};

function CardList({ tasks }: { tasks: task[] }) {
return (
Expand Down Expand Up @@ -41,6 +104,8 @@ const Content = () => {

const Mine: FC = () => {
const { isLoading: isAuthenticating, isLoggedIn } = useAuthenticated();
const router = useRouter();
const dev = router.query.dev === 'true' ? true : false;

return (
<Layout>
Expand All @@ -49,7 +114,11 @@ const Mine: FC = () => {
{isAuthenticating ? (
<Loader />
) : isLoggedIn ? (
<Content />
dev ? (
<ContentDev />
) : (
<Content />
)
) : (
<div>
<p>You are not Authorized</p>
Expand Down
16 changes: 16 additions & 0 deletions src/utils/getFilteredTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import task, { Tab } from '@/interfaces/task.type';

export default function getFilteredTasks(
tasks: task[],
status: Tab,
title: string
) {
let filteredTasks = tasks;

if (status !== Tab.ALL)
filteredTasks = filteredTasks.filter((task) => task.status === status);
if (title !== '')
filteredTasks = filteredTasks.filter((task) => task.title === title);

return filteredTasks;
}
12 changes: 12 additions & 0 deletions src/utils/getInputValueFromTaskField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Tab } from '@/interfaces/task.type';
import { getQueryParamTab, getQueryParamTitle } from './taskQueryParams';

export default function getInputValueFromTaskField(tab: Tab, title: string) {
let inputValue = '';

inputValue += `${getQueryParamTab(tab)} `;

inputValue += `${getQueryParamTitle(title)} `;

return inputValue.trim();
}
Loading