Skip to content

Commit

Permalink
Merge pull request #120 from Real-Dev-Squad/develop
Browse files Browse the repository at this point in the history
dev to main merge
  • Loading branch information
iamitprakash authored Jul 21, 2024
2 parents 0fc56e4 + 45a6cb2 commit d56f64a
Show file tree
Hide file tree
Showing 18 changed files with 305 additions and 134 deletions.
10 changes: 8 additions & 2 deletions __mocks__/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { getAllUrlHandler, getOriginalUrlHandler, shortenUrlHandler } from './handlers/url';
import { deleteUrlHandler, getAllUrlHandler, getOriginalUrlHandler, shortenUrlHandler } from './handlers/url';
import { getSelfUserHandler } from './handlers/user';

const handlers = [...getSelfUserHandler, ...getAllUrlHandler, ...getOriginalUrlHandler, ...shortenUrlHandler];
const handlers = [
...deleteUrlHandler,
...getSelfUserHandler,
...getAllUrlHandler,
...getOriginalUrlHandler,
...shortenUrlHandler,
];
export default handlers;
7 changes: 7 additions & 0 deletions __mocks__/handlers/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ const shortenUrlHandler = [
}),
];

const deleteUrlHandler = [
rest.delete(`${TINY_API_URL}/urls/:id`, (_, res, ctx) => {
return res(ctx.status(200), ctx.json({ success: true }));
}),
];

export {
deleteUrlHandler,
getAllUrlHandler,
getOriginalUrlHandler,
notFoundAllUrlHandler,
Expand Down
38 changes: 38 additions & 0 deletions __tests__/components/Dashhboard/DashboardLayout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { render, screen } from '@testing-library/react';

import { DashboardLayout } from '@/components/Dashboard/dashboard-layout';

describe('DashboardLayout', () => {
test('renders DashboardLayout with children', () => {
render(
<DashboardLayout remainingUrls={10}>
<div>Test Child Component</div>
</DashboardLayout>
);
expect(screen.getByText('Your URLs')).toBeInTheDocument();
expect(screen.getByText('Remaining: 10 / 50')).toBeInTheDocument();
expect(screen.getByText('Test Child Component')).toBeInTheDocument();
});

test('renders DashboardLayout without remainingUrls', () => {
render(
<DashboardLayout>
<div>Test Child Component</div>
</DashboardLayout>
);
expect(screen.getByText('Your URLs')).toBeInTheDocument();
expect(screen.queryByText(/Remaining:/)).toBeNull();
expect(screen.getByText('Test Child Component')).toBeInTheDocument();
});

test('renders DashboardLayout with maximum URLs', () => {
render(
<DashboardLayout remainingUrls={50}>
<div>Test Child Component</div>
</DashboardLayout>
);
expect(screen.getByText('Your URLs')).toBeInTheDocument();
expect(screen.getByText('Remaining: 50 / 50')).toBeInTheDocument();
expect(screen.getByText('Test Child Component')).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion __tests__/components/Dashhboard/NoUrlFound.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('NoUrlFound', () => {
<NoUrlFound />
</QueryClientProvider>
);
const noUrlFoundText = screen.getByText(/No URLs found/i);
const noUrlFoundText = screen.getByText(/Oops! We couldn't find any URLs/i);
expect(noUrlFoundText).toBeInTheDocument();
const createOneButton = screen.getByText(/Create one/i);
expect(createOneButton).toBeInTheDocument();
Expand Down
23 changes: 0 additions & 23 deletions __tests__/components/Dashhboard/UrlList.test.tsx

This file was deleted.

62 changes: 59 additions & 3 deletions __tests__/components/Dashhboard/UrlListItem.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from 'react-query';

import UrlListItem from '@/components/Dashboard/UrlListItem';
import useAuthenticated from '@/hooks/useAuthenticated';
import { deleteUrlApi } from '@/services/api';

import { urls } from '../../../__mocks__/db/urls';

jest.mock('@/hooks/useAuthenticated');
jest.mock('@/services/api');

describe('UrlListItem', () => {
const queryClient = new QueryClient();
const url = urls.urls[0];
const copyButtonHandler = (url: string) => {
const copyButtonHandler = (url) => {
navigator.clipboard.writeText(url);
};
const mockWriteText = jest.fn();
global.navigator.clipboard = { writeText: mockWriteText };

beforeEach(() => {
useAuthenticated.mockReturnValue({
userData: { data: { id: 'user123' } },
});
});

test('renders UrlListItem component', () => {
render(
<QueryClientProvider client={queryClient}>
Expand Down Expand Up @@ -41,7 +52,52 @@ describe('UrlListItem', () => {
</QueryClientProvider>
);
const copyButton = screen.getByTestId('copy-button');
copyButton.click();
fireEvent.click(copyButton);
expect(mockWriteText).toHaveBeenCalledWith(url.originalUrl);
});

test('delete button works', async () => {
deleteUrlApi.mockResolvedValueOnce({});

render(
<QueryClientProvider client={queryClient}>
<UrlListItem
url={url}
copyButtonHandler={() => {
copyButtonHandler(url.originalUrl);
}}
/>
</QueryClientProvider>
);
const deleteButton = screen.getByText('Delete');
fireEvent.click(deleteButton);

await waitFor(() => {
expect(deleteUrlApi).toHaveBeenCalledWith({ id: url.id, userId: 'user123' });
});
});

test('shows error on delete failure', async () => {
const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => 'Alert called');
deleteUrlApi.mockRejectedValueOnce(new Error('Error deleting URL'));

render(
<QueryClientProvider client={queryClient}>
<UrlListItem
url={url}
copyButtonHandler={() => {
copyButtonHandler(url.originalUrl);
}}
/>
</QueryClientProvider>
);
const deleteButton = screen.getByText('Delete');
fireEvent.click(deleteButton);

await waitFor(() => {
expect(alertMock).toHaveBeenCalledWith('Error deleting URL');
});

alertMock.mockRestore();
});
});
47 changes: 33 additions & 14 deletions __tests__/hooks/api.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { QueryClient, QueryClientProvider } from 'react-query';

import { useAuthenticatedQuery, useGetOriginalUrlQuery, useGetUrlsQuery, useShortenUrlMutation } from '@/services/api';

import { urlDetails } from '../../__mocks__/db/urls';
import { urls } from '../../__mocks__/db/urls';
import {
deleteUrlApi,
useAuthenticatedQuery,
useGetOriginalUrlQuery,
useGetUrlsQuery,
useShortenUrlMutation,
} from '@/services/api';

import { urlDetails, urls } from '../../__mocks__/db/urls';
import user from '../../__mocks__/db/user';
import notFoundOriginalUrlHandler from '../../__mocks__/handler';
import notFoundAllUrlHandler from '../../__mocks__/handler';
Expand Down Expand Up @@ -63,16 +68,14 @@ describe('useGetOriginalUrlQuery', () => {
});

describe('useGetUrlsQuery', () => {
const userId = user.data.id.toString();

it('returns isLoading as true by default', () => {
const { result } = renderHook(() => useGetUrlsQuery(userId, { enabled: true }), { wrapper });
const { result } = renderHook(() => useGetUrlsQuery({ enabled: true }), { wrapper });

expect(result.current.isLoading).toBe(true);
});

it('returns isLoading as false and data as urls data when urls are found', async () => {
const { result, waitFor } = renderHook(() => useGetUrlsQuery(userId, { enabled: true }), {
const { result, waitFor } = renderHook(() => useGetUrlsQuery({ enabled: true }), {
wrapper,
});

Expand All @@ -88,7 +91,7 @@ describe('useGetUrlsQuery', () => {

it('returns isLoading as false and isError as true when urls are not found', async () => {
server.use(...notFoundAllUrlHandler);
const { result, waitFor } = renderHook(() => useGetUrlsQuery('123', { enabled: true }), {
const { result, waitFor } = renderHook(() => useGetUrlsQuery({ enabled: true }), {
wrapper,
});

Expand All @@ -101,21 +104,37 @@ describe('useGetUrlsQuery', () => {

describe('useShortenUrlMutation', () => {
it('should return data after successfully shortening the URL', async () => {
const userData = user.data;
const userData = user;
const originalUrl = urlDetails.url.originalUrl;

server.use(...handlers);
const { result, waitFor } = renderHook(() => useShortenUrlMutation(), { wrapper });

act(() => {
await act(async () => {
result.current.mutate({ originalUrl, userData });
});

await waitFor(() => result.current.isSuccess);

await waitFor(() => {
expect(result.current.data).toEqual(urlDetails.url.shortUrl);
expect(result.current.isSuccess).toBe(true);
const shortUrlFromApi = result.current.data?.shortUrl;

expect(shortUrlFromApi).toBeDefined();
expect(shortUrlFromApi).toEqual(urlDetails.url.shortUrl);
expect(result.current.isSuccess).toBe(true);
});
});

describe('deleteUrlApi', () => {
it('should delete URL successfully', async () => {
server.use(...handlers);

const id = 1;
const userId = user.data.id;

await act(async () => {
const result = await deleteUrlApi({ id, userId });
expect(result).toBeDefined();
expect(result.success).toBe(true);
});
});
});
5 changes: 3 additions & 2 deletions __tests__/pages/dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ describe('Dashboard', () => {
<Dashboard />
</QueryClientProvider>
);
expect(screen.getByText('No URLs found')).toBeInTheDocument();
expect(screen.getByText('Create one')).toBeInTheDocument();
// eslint-disable-next-line quotes
expect(screen.getByText("Oops! We couldn't find any URLs.")).toBeInTheDocument();
expect(screen.getByText('Create a URL')).toBeInTheDocument();
});

it('shows toast message when copy button is clicked', () => {
Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const createJestConfig = nextJest({
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
snapshotSerializers: [],
};

Expand Down
12 changes: 9 additions & 3 deletions src/components/Dashboard/NoUrlFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import Link from 'next/link';
const NoUrlFound = () => {
return (
<div className="w-full flex flex-col justify-center items-center p-4 text-white bg-gray-900 min-h-[86vh] ">
<p className="text-white">No URLs found</p>
<Link href="/" className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg mt-4">
Create one
<h5 className="text-white text-2xl pb-2 font-semibold text-center leading-relaxed">
<span className="block">Oops! We couldn't find any URLs.</span>
<span className="block">Would you like to create one?</span>
</h5>
<Link
href="/"
className="bg-blue-600 hover:bg-blue-700 active:scale-95 transition text-white font-semibold py-2 px-4 rounded-lg mt-4"
>
Create a URL
</Link>
</div>
);
Expand Down
33 changes: 0 additions & 33 deletions src/components/Dashboard/UrlList.tsx

This file was deleted.

5 changes: 3 additions & 2 deletions src/components/Dashboard/UrlListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TbTrash, TbWorldWww } from 'react-icons/tb';
import { useMutation } from 'react-query';

import { TINY_SITE } from '@/constants/url';
import useAuthenticated from '@/hooks/useAuthenticated';
import { queryClient } from '@/pages/_app';
import { deleteUrlApi } from '@/services/api';
import { UrlType } from '@/types/url.types';
Expand All @@ -29,6 +30,7 @@ const UrlListItem = ({ url, copyButtonHandler }: UrlListItemProps) => {
console.error(error);
},
});
const { userData } = useAuthenticated();

return (
<li className="rounded-lg grid grid-cols-1 sm:grid-cols-[1fr,32px] bg-white w-full px-2 py-2 sm:px-4 sm:py-2 items-center flex-wrap">
Expand Down Expand Up @@ -89,7 +91,7 @@ const UrlListItem = ({ url, copyButtonHandler }: UrlListItemProps) => {
<div className="w-full grid place-items-center pt-5 sm:pt-0">
<Button
disabled={deleteMutation.isLoading}
onClick={() => deleteMutation.mutate({ id: url.id })}
onClick={() => deleteMutation.mutate({ id: url.id, userId: userData?.data?.id })}
className={`flex items-center justify-center gap-2 w-full sm:w-8 h-8 rounded transition active:scale-95 ${
deleteMutation.isLoading
? 'text-gray-600 bg-gray-100'
Expand All @@ -100,7 +102,6 @@ const UrlListItem = ({ url, copyButtonHandler }: UrlListItemProps) => {
{!deleteMutation.isLoading && <TbTrash className="w-4 h-4 sm:w-5 sm:h-5" />}
{deleteMutation.isLoading && <Loader className="w-4 h-4 sm:w-5 sm:h-5 text-gray-400" />}
</div>

<span className="text-xs font-medium sm:hidden">Delete</span>
</Button>
</div>
Expand Down
Loading

0 comments on commit d56f64a

Please sign in to comment.