From 9dce1d5b4849d7bfca8f74670d72eee56991d08d Mon Sep 17 00:00:00 2001 From: towfiqi Date: Sun, 12 Nov 2023 20:58:03 +0600 Subject: [PATCH] chore: resolves test issues --- .eslintrc.json | 1 + Dockerfile | 2 +- {__test__ => __mocks__}/data.ts | 26 +++- {__test__ => __mocks__}/utils.tsx | 0 __test__/components/Modal.test.tsx | 35 ----- __test__/components/Sidebar.test.tsx | 11 -- __test__/hooks/domains.tests.tsx | 21 --- __tests__/components/DomainItem.test.tsx | 35 +++++ .../components/Icon.test.tsx | 0 .../components/Keyword.test.tsx | 30 ++-- __tests__/components/Modal.test.tsx | 28 ++++ __tests__/components/Sidebar.test.tsx | 23 +++ .../components/Topbar.test.tsx | 8 +- __tests__/hooks/domains.test.tsx | 27 ++++ {__test__ => __tests__}/pages/domain.test.tsx | 131 ++++++++++-------- __tests__/pages/domains.test.tsx | 49 +++++++ {__test__ => __tests__}/pages/index.test.tsx | 23 ++- components/settings/Settings.tsx | 2 +- jest.setup.js | 17 ++- package-lock.json | 37 +++++ package.json | 2 + pages/domains/index.tsx | 7 +- pages/index.tsx | 2 +- services/settings.ts | 4 +- tsconfig.json | 3 +- 25 files changed, 359 insertions(+), 165 deletions(-) rename {__test__ => __mocks__}/data.ts (73%) rename {__test__ => __mocks__}/utils.tsx (100%) delete mode 100644 __test__/components/Modal.test.tsx delete mode 100644 __test__/components/Sidebar.test.tsx delete mode 100644 __test__/hooks/domains.tests.tsx create mode 100644 __tests__/components/DomainItem.test.tsx rename {__test__ => __tests__}/components/Icon.test.tsx (100%) rename {__test__ => __tests__}/components/Keyword.test.tsx (68%) create mode 100644 __tests__/components/Modal.test.tsx create mode 100644 __tests__/components/Sidebar.test.tsx rename {__test__ => __tests__}/components/Topbar.test.tsx (64%) create mode 100644 __tests__/hooks/domains.test.tsx rename {__test__ => __tests__}/pages/domain.test.tsx (61%) create mode 100644 __tests__/pages/domains.test.tsx rename {__test__ => __tests__}/pages/index.test.tsx (57%) diff --git a/.eslintrc.json b/.eslintrc.json index 825baa9..3a6c420 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,7 @@ "no-await-in-loop": "off", "arrow-body-style":"off", "max-len": ["error", {"code": 150, "ignoreComments": true, "ignoreUrls": true}], + "import/no-extraneous-dependencies": "off", "import/extensions": [ "error", "ignorePackages", diff --git a/Dockerfile b/Dockerfile index 2854e37..1408031 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ FROM node:lts-alpine AS builder WORKDIR /app COPY --from=deps /app ./ RUN rm -rf /app/data -RUN rm -rf /app/__test__ +RUN rm -rf /app/__tests__ RUN npm run build diff --git a/__test__/data.ts b/__mocks__/data.ts similarity index 73% rename from __test__/data.ts rename to __mocks__/data.ts index 82e7f31..81226cb 100644 --- a/__test__/data.ts +++ b/__mocks__/data.ts @@ -2,10 +2,11 @@ export const dummyDomain = { ID: 1, domain: 'compressimage.io', slug: 'compressimage-io', - keywordCount: 0, + keywordCount: 10, + avgPosition: 24, lastUpdated: '2022-11-11T10:00:32.243', added: '2022-11-11T10:00:32.244', - tags: [], + tags: '', notification: true, notification_interval: 'daily', notification_emails: '', @@ -33,7 +34,7 @@ export const dummyKeywords = [ lastResult: [], sticky: false, updating: false, - lastUpdateError: 'false', + lastUpdateError: false as false, }, { ID: 2, @@ -56,6 +57,23 @@ export const dummyKeywords = [ lastResult: [], sticky: false, updating: false, - lastUpdateError: 'false', + lastUpdateError: false as false, }, ]; + +export const dummySettings = { + scaping_api: '', + scraper_type: 'none', + notification_interval: 'never', + notification_email: '', + notification_email_from: '', + smtp_server: '', + smtp_port: '', + smtp_username: '', + smtp_password: '', + scrape_retry: false, + search_console_integrated: false, + screenshot_key: '', + available_scapers: [], + failed_queue: [], +}; diff --git a/__test__/utils.tsx b/__mocks__/utils.tsx similarity index 100% rename from __test__/utils.tsx rename to __mocks__/utils.tsx diff --git a/__test__/components/Modal.test.tsx b/__test__/components/Modal.test.tsx deleted file mode 100644 index 51605cd..0000000 --- a/__test__/components/Modal.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import Modal from '../../components/common/Modal'; - -// jest.mock('React', () => ({ -// ...jest.requireActual('React'), -// useEffect: jest.fn(), -// })); -// const mockedUseEffect = useEffect as jest.Mock; - -// jest.mock('../../components/common/Icon', () => () =>
); - -describe('Modal Component', () => { - it('Renders without crashing', async () => { - render( console.log() }>
); - // mockedUseEffect.mock.calls[0](); - expect(document.querySelector('.modal')).toBeInTheDocument(); - }); -// it('Sets up the escapae key shortcut', async () => { -// render( console.log() }>
); -// expect(mockedUseEffect).toBeCalled(); -// }); - it('Displays the Given Content', async () => { - render( console.log() }> -
-

Hello Modal!!

-
-
); - expect(await screen.findByText('Hello Modal!!')).toBeInTheDocument(); - }); - it('Renders Modal Title', async () => { - render( console.log() } title="Sample Modal Title">

Some Modal Content

); - expect(await screen.findByText('Sample Modal Title')).toBeInTheDocument(); - }); -}); diff --git a/__test__/components/Sidebar.test.tsx b/__test__/components/Sidebar.test.tsx deleted file mode 100644 index a5845ea..0000000 --- a/__test__/components/Sidebar.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import Sidebar from '../../components/common/Sidebar'; - -describe('Sidebar Component', () => { - it('renders without crashing', async () => { - render( console.log() } />); - expect( - await screen.findByText('SerpBear'), - ).toBeInTheDocument(); - }); -}); diff --git a/__test__/hooks/domains.tests.tsx b/__test__/hooks/domains.tests.tsx deleted file mode 100644 index 9244bd0..0000000 --- a/__test__/hooks/domains.tests.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { waitFor } from '@testing-library/react'; -// import { useFetchDomains } from '../../services/domains'; -// import { createWrapper } from '../utils'; - -jest.mock('next/router', () => ({ - useRouter: () => ({ - query: { slug: 'compressimage-io' }, - push: (link:string) => { console.log('Pushed', link); }, - }), -})); - -describe('DomainHooks', () => { - it('useFetchDomains should fetch the Domains', async () => { - // const { result } = renderHook(() => useFetchDomains(), { wrapper: createWrapper() }); - const result = { current: { isSuccess: false, data: '' } }; - await waitFor(() => { - console.log('result.current: ', result.current.data); - return expect(result.current.isSuccess).toBe(true); - }); - }); -}); diff --git a/__tests__/components/DomainItem.test.tsx b/__tests__/components/DomainItem.test.tsx new file mode 100644 index 0000000..985c1d3 --- /dev/null +++ b/__tests__/components/DomainItem.test.tsx @@ -0,0 +1,35 @@ +import { fireEvent, render } from '@testing-library/react'; +import DomainItem from '../../components/domains/DomainItem'; +import { dummyDomain } from '../../__mocks__/data'; + +const updateThumbMock = jest.fn(); +const domainItemProps = { + domain: dummyDomain, + selected: false, + isConsoleIntegrated: false, + thumb: '', + updateThumb: updateThumbMock, +}; + +describe('DomainItem Component', () => { + it('renders without crashing', async () => { + const { container } = render(); + expect(container.querySelector('.domItem')).toBeInTheDocument(); + }); + it('renders keywords count', async () => { + const { container } = render(); + const domStatskeywords = container.querySelector('.dom_stats div:nth-child(1)'); + expect(domStatskeywords?.textContent).toBe('Keywords10'); + }); + it('renders avg position', async () => { + const { container } = render(); + const domStatsAvg = container.querySelector('.dom_stats div:nth-child(2)'); + expect(domStatsAvg?.textContent).toBe('Avg position24'); + }); + it('updates domain thumbnail on relevant button click', async () => { + const { container } = render(); + const reloadThumbbBtn = container.querySelector('.domain_thumb button'); + if (reloadThumbbBtn) fireEvent.click(reloadThumbbBtn); + expect(updateThumbMock).toHaveBeenCalledWith(dummyDomain.domain); + }); +}); diff --git a/__test__/components/Icon.test.tsx b/__tests__/components/Icon.test.tsx similarity index 100% rename from __test__/components/Icon.test.tsx rename to __tests__/components/Icon.test.tsx diff --git a/__test__/components/Keyword.test.tsx b/__tests__/components/Keyword.test.tsx similarity index 68% rename from __test__/components/Keyword.test.tsx rename to __tests__/components/Keyword.test.tsx index e3994c1..9e09eb0 100644 --- a/__test__/components/Keyword.test.tsx +++ b/__tests__/components/Keyword.test.tsx @@ -1,8 +1,14 @@ import { fireEvent, render, screen } from '@testing-library/react'; import Keyword from '../../components/keywords/Keyword'; -import { dummyKeywords } from '../data'; +import { dummyKeywords } from '../../__mocks__/data'; -const keywordFunctions = { +const keywordProps = { + keywordData: dummyKeywords[0], + selected: false, + index: 0, + showSCData: false, + scDataType: '', + style: {}, refreshkeyword: jest.fn(), favoriteKeyword: jest.fn(), removeKeyword: jest.fn(), @@ -10,35 +16,37 @@ const keywordFunctions = { manageTags: jest.fn(), showKeywordDetails: jest.fn(), }; - +jest.mock('react-chartjs-2', () => ({ + Line: () => null, + })); describe('Keyword Component', () => { it('renders without crashing', async () => { - render(); + render(); expect(await screen.findByText('compress image')).toBeInTheDocument(); }); it('Should Render Position Correctly', async () => { - render(); + render(); const positionElement = document.querySelector('.keyword_position'); expect(positionElement?.childNodes[0].nodeValue).toBe('19'); }); it('Should Display Position Change arrow', async () => { - render(); + render(); const positionElement = document.querySelector('.keyword_position i'); expect(positionElement?.textContent).toBe('▲ 1'); }); it('Should Display the SERP Page URL', async () => { - render(); + render(); const positionElement = document.querySelector('.keyword_url'); expect(positionElement?.textContent).toBe('/'); }); it('Should Display the Keyword Options on dots Click', async () => { - render(); - const button = document.querySelector('.keyword .keyword_dots'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + const { container } = render(); + const button = container.querySelector('.keyword_dots'); + if (button) fireEvent.click(button); expect(document.querySelector('.keyword_options')).toBeVisible(); }); // it('Should favorite Keywords', async () => { - // render(); + // render(); // const button = document.querySelector('.keyword .keyword_dots'); // if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); // const option = document.querySelector('.keyword .keyword_options li:nth-child(1) a'); diff --git a/__tests__/components/Modal.test.tsx b/__tests__/components/Modal.test.tsx new file mode 100644 index 0000000..d0f975a --- /dev/null +++ b/__tests__/components/Modal.test.tsx @@ -0,0 +1,28 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import Modal from '../../components/common/Modal'; + +const closeModalMock = jest.fn(); +describe('Modal Component', () => { + it('Renders without crashing', async () => { + render(
); + expect(document.querySelector('.modal')).toBeInTheDocument(); + }); + it('Displays the Given Content', async () => { + render( +
+

Hello Modal!!

+
+
); + expect(await screen.findByText('Hello Modal!!')).toBeInTheDocument(); + }); + it('Renders Modal Title', async () => { + render(

Some Modal Content

); + expect(await screen.findByText('Sample Modal Title')).toBeInTheDocument(); + }); + it('Closes the modal on close button click', async () => { + const { container } = render(

Some Modal Content

); + const closeBtn = container.querySelector('.modal-close'); + if (closeBtn) fireEvent.click(closeBtn); + expect(closeModalMock).toHaveBeenCalled(); + }); +}); diff --git a/__tests__/components/Sidebar.test.tsx b/__tests__/components/Sidebar.test.tsx new file mode 100644 index 0000000..f026217 --- /dev/null +++ b/__tests__/components/Sidebar.test.tsx @@ -0,0 +1,23 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import Sidebar from '../../components/common/Sidebar'; +import { dummyDomain } from '../../__mocks__/data'; + +const addDomainMock = jest.fn(); +jest.mock('next/router', () => jest.requireActual('next-router-mock')); + +describe('Sidebar Component', () => { + it('renders without crashing', async () => { + render(); + expect(screen.getByText('SerpBear')).toBeInTheDocument(); + }); + it('renders domain list', async () => { + render(); + expect(screen.getByText('compressimage.io')).toBeInTheDocument(); + }); + it('calls showAddModal on Add Domain button click', async () => { + render(); + const addDomainBtn = screen.getByTestId('add_domain'); + fireEvent.click(addDomainBtn); + expect(addDomainMock).toHaveBeenCalledWith(true); + }); +}); diff --git a/__test__/components/Topbar.test.tsx b/__tests__/components/Topbar.test.tsx similarity index 64% rename from __test__/components/Topbar.test.tsx rename to __tests__/components/Topbar.test.tsx index 2881dd0..296918e 100644 --- a/__test__/components/Topbar.test.tsx +++ b/__tests__/components/Topbar.test.tsx @@ -1,9 +1,15 @@ import { render, screen } from '@testing-library/react'; import TopBar from '../../components/common/TopBar'; +jest.mock('next/router', () => ({ + useRouter: () => ({ + pathname: '/', + }), +})); + describe('TopBar Component', () => { it('renders without crashing', async () => { - render( console.log() } />); + render(); expect( await screen.findByText('SerpBear'), ).toBeInTheDocument(); diff --git a/__tests__/hooks/domains.test.tsx b/__tests__/hooks/domains.test.tsx new file mode 100644 index 0000000..1d9bdf1 --- /dev/null +++ b/__tests__/hooks/domains.test.tsx @@ -0,0 +1,27 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import mockRouter from 'next-router-mock'; + +import { useFetchDomains } from '../../services/domains'; +import { createWrapper } from '../../__mocks__/utils'; +import { dummyDomain } from '../../__mocks__/data'; + +jest.mock('next/router', () => jest.requireActual('next-router-mock')); + +fetchMock.mockIf(`${window.location.origin}/api/domains`, async () => { + return new Promise((resolve) => { + resolve({ + body: JSON.stringify({ domains: [dummyDomain] }), + status: 200, + }); + }); +}); + +describe('DomainHooks', () => { + it('useFetchDomains should fetch the Domains', async () => { + const { result } = renderHook(() => useFetchDomains(mockRouter), { wrapper: createWrapper() }); + // const result = { current: { isSuccess: false, data: '' } }; + await waitFor(() => { + return expect(result.current.isLoading).toBe(false); + }); + }); +}); diff --git a/__test__/pages/domain.test.tsx b/__tests__/pages/domain.test.tsx similarity index 61% rename from __test__/pages/domain.test.tsx rename to __tests__/pages/domain.test.tsx index 4fc3495..13ace7b 100644 --- a/__test__/pages/domain.test.tsx +++ b/__tests__/pages/domain.test.tsx @@ -1,17 +1,26 @@ import { fireEvent, render, screen } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { MockResponseInitFunction } from 'jest-fetch-mock'; import SingleDomain from '../../pages/domain/[slug]'; import { useAddDomain, useDeleteDomain, useFetchDomains, useUpdateDomain } from '../../services/domains'; import { useAddKeywords, useDeleteKeywords, useFavKeywords, useFetchKeywords, useRefreshKeywords } from '../../services/keywords'; -import { dummyDomain, dummyKeywords } from '../data'; +import { dummyDomain, dummyKeywords, dummySettings } from '../../__mocks__/data'; +import { useFetchSettings } from '../../services/settings'; jest.mock('../../services/domains'); jest.mock('../../services/keywords'); +jest.mock('../../services/settings'); + jest.mock('next/router', () => ({ useRouter: () => ({ query: { slug: dummyDomain.slug }, }), })); +jest.mock('react-chartjs-2', () => ({ + Line: () => null, +})); + const useFetchDomainsFunc = useFetchDomains as jest.Mock; const useFetchKeywordsFunc = useFetchKeywords as jest.Mock; const useDeleteKeywordsFunc = useDeleteKeywords as jest.Mock; @@ -21,9 +30,12 @@ const useAddDomainFunc = useAddDomain as jest.Mock; const useAddKeywordsFunc = useAddKeywords as jest.Mock; const useUpdateDomainFunc = useUpdateDomain as jest.Mock; const useDeleteDomainFunc = useDeleteDomain as jest.Mock; +const useFetchSettingsFunc = useFetchSettings as jest.Mock; describe('SingleDomain Page', () => { + const queryClient = new QueryClient(); beforeEach(() => { + useFetchSettingsFunc.mockImplementation(() => ({ data: { settings: dummySettings }, isLoading: false })); useFetchDomainsFunc.mockImplementation(() => ({ data: { domains: [dummyDomain] }, isLoading: false })); useFetchKeywordsFunc.mockImplementation(() => ({ keywordsData: { keywords: dummyKeywords }, keywordsLoading: false })); useDeleteKeywordsFunc.mockImplementation(() => ({ mutate: () => { } })); @@ -38,158 +50,163 @@ describe('SingleDomain Page', () => { jest.clearAllMocks(); }); it('Render without crashing.', async () => { - const { getByTestId } = render(); - // screen.debug(undefined, Infinity); - expect(getByTestId('domain-header')).toBeInTheDocument(); - // expect(await result.findByText(/compressimage/i)).toBeInTheDocument(); + render(); + expect(screen.getByTestId('domain-header')).toBeInTheDocument(); }); it('Should Call the useFetchDomains hook on render.', async () => { - render(); - // screen.debug(undefined, Infinity); + render(); expect(useFetchDomains).toHaveBeenCalled(); - // expect(await result.findByText(/compressimage/i)).toBeInTheDocument(); }); it('Should Render the Keywords', async () => { - render(); + render(); const keywordsCount = document.querySelectorAll('.keyword').length; expect(keywordsCount).toBe(2); }); it('Should Display the Keywords Details Sidebar on Keyword Click.', async () => { - render(); + render(); const keywords = document.querySelectorAll('.keyword'); const firstKeyword = keywords && keywords[0].querySelector('a'); - if (firstKeyword) fireEvent(firstKeyword, new MouseEvent('click', { bubbles: true })); + if (firstKeyword) fireEvent.click(firstKeyword); + const fn: MockResponseInitFunction = async () => { + return new Promise((resolve) => { + resolve({ + body: JSON.stringify({ keyword: dummyKeywords[0] }), + status: 200, + }); + }); + }; + fetchMock.mockIf(`${window.location.origin}/api/keyword?id=${dummyKeywords[0].ID}`, fn); + expect(screen.getByTestId('keywordDetails')).toBeVisible(); }); it('Should Display the AddDomain Modal on Add Domain Button Click.', async () => { - render(); - const button = document.querySelector('[data-testid=add_domain]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('add_domain'); + if (button) fireEvent.click(button); expect(screen.getByTestId('adddomain_modal')).toBeVisible(); }); it('Should Display the AddKeywords Modal on Add Keyword Button Click.', async () => { - render(); - const button = document.querySelector('[data-testid=add_keyword]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('add_keyword'); + if (button) fireEvent.click(button); expect(screen.getByTestId('addkeywords_modal')).toBeVisible(); }); it('Should display the Domain Settings on Settings Button click.', async () => { - render(); - const button = document.querySelector('[data-testid=show_domain_settings]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('show_domain_settings'); + if (button) fireEvent.click(button); expect(screen.getByTestId('domain_settings')).toBeVisible(); }); it('Device Tab change should be functioning.', async () => { - render(); - const button = document.querySelector('[data-testid=mobile_tab]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('mobile_tab'); + if (button) fireEvent.click(button); const keywordsCount = document.querySelectorAll('.keyword').length; expect(keywordsCount).toBe(0); }); it('Search Filter should function properly', async () => { - render(); + render(); const inputNode = screen.getByTestId('filter_input'); - fireEvent.change(inputNode, { target: { value: 'compressor' } }); // triggers onChange event + if (inputNode) fireEvent.change(inputNode, { target: { value: 'compressor' } }); // triggers onChange event expect(inputNode.getAttribute('value')).toBe('compressor'); const keywordsCount = document.querySelectorAll('.keyword').length; expect(keywordsCount).toBe(1); }); it('Country Filter should function properly', async () => { - render(); - const button = document.querySelector('[data-testid=filter_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('filter_button'); + if (button) fireEvent.click(button); expect(document.querySelector('.country_filter')).toBeVisible(); const countrySelect = document.querySelector('.country_filter .selected'); - if (countrySelect) fireEvent(countrySelect, new MouseEvent('click', { bubbles: true })); + if (countrySelect) fireEvent.click(countrySelect); expect(document.querySelector('.country_filter .select_list')).toBeVisible(); const firstCountry = document.querySelector('.country_filter .select_list ul li:nth-child(1)'); - if (firstCountry) fireEvent(firstCountry, new MouseEvent('click', { bubbles: true })); + if (firstCountry) fireEvent.click(firstCountry); const keywordsCount = document.querySelectorAll('.keyword').length; expect(keywordsCount).toBe(0); }); // Tags Filter should function properly it('Tags Filter should Render & Function properly', async () => { - render(); - const button = document.querySelector('[data-testid=filter_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('filter_button'); + if (button) fireEvent.click(button); expect(document.querySelector('.tags_filter')).toBeVisible(); const countrySelect = document.querySelector('.tags_filter .selected'); - if (countrySelect) fireEvent(countrySelect, new MouseEvent('click', { bubbles: true })); + if (countrySelect) fireEvent.click(countrySelect); expect(document.querySelector('.tags_filter .select_list')).toBeVisible(); expect(document.querySelectorAll('.tags_filter .select_list ul li').length).toBe(1); const firstTag = document.querySelector('.tags_filter .select_list ul li:nth-child(1)'); - if (firstTag) fireEvent(firstTag, new MouseEvent('click', { bubbles: true })); + if (firstTag) fireEvent.click(firstTag); expect(document.querySelectorAll('.keyword').length).toBe(1); }); it('Sort Options Should be visible Sort Button on Click.', async () => { - render(); - const button = document.querySelector('[data-testid=sort_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('sort_button'); + if (button) fireEvent.click(button); expect(document.querySelector('.sort_options')).toBeVisible(); }); it('Sort: Position should sort keywords accordingly', async () => { - render(); - const button = document.querySelector('[data-testid=sort_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); - + render(); + const button = screen.getByTestId('sort_button'); + if (button) fireEvent.click(button); // Test Top Position Sort const topPosSortOption = document.querySelector('ul.sort_options li:nth-child(1)'); - if (topPosSortOption) fireEvent(topPosSortOption, new MouseEvent('click', { bubbles: true })); + if (topPosSortOption) fireEvent.click(topPosSortOption); const firstKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(firstKeywordTitle).toBe('compress image'); // Test Lowest Position Sort - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + if (button) fireEvent.click(button); const lowestPosSortOption = document.querySelector('ul.sort_options li:nth-child(2)'); - if (lowestPosSortOption) fireEvent(lowestPosSortOption, new MouseEvent('click', { bubbles: true })); + if (lowestPosSortOption) fireEvent.click(lowestPosSortOption); const secondKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(secondKeywordTitle).toBe('image compressor'); }); it('Sort: Date Added should sort keywords accordingly', async () => { - render(); - const button = document.querySelector('[data-testid=sort_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('sort_button'); + if (button) fireEvent.click(button); // Test Top Position Sort const topPosSortOption = document.querySelector('ul.sort_options li:nth-child(3)'); - if (topPosSortOption) fireEvent(topPosSortOption, new MouseEvent('click', { bubbles: true })); + if (topPosSortOption) fireEvent.click(topPosSortOption); const firstKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(firstKeywordTitle).toBe('compress image'); // Test Lowest Position Sort - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + if (button) fireEvent.click(button); const lowestPosSortOption = document.querySelector('ul.sort_options li:nth-child(4)'); - if (lowestPosSortOption) fireEvent(lowestPosSortOption, new MouseEvent('click', { bubbles: true })); + if (lowestPosSortOption) fireEvent.click(lowestPosSortOption); const secondKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(secondKeywordTitle).toBe('image compressor'); }); it('Sort: Alphabetical should sort keywords accordingly', async () => { - render(); - const button = document.querySelector('[data-testid=sort_button]'); - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + render(); + const button = screen.getByTestId('sort_button'); + if (button) fireEvent.click(button); // Test Top Position Sort const topPosSortOption = document.querySelector('ul.sort_options li:nth-child(5)'); - if (topPosSortOption) fireEvent(topPosSortOption, new MouseEvent('click', { bubbles: true })); + if (topPosSortOption) fireEvent.click(topPosSortOption); const firstKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(firstKeywordTitle).toBe('compress image'); // Test Lowest Position Sort - if (button) fireEvent(button, new MouseEvent('click', { bubbles: true })); + if (button) fireEvent.click(button); const lowestPosSortOption = document.querySelector('ul.sort_options li:nth-child(6)'); - if (lowestPosSortOption) fireEvent(lowestPosSortOption, new MouseEvent('click', { bubbles: true })); + if (lowestPosSortOption) fireEvent.click(lowestPosSortOption); const secondKeywordTitle = document.querySelector('.domKeywords_keywords .keyword:nth-child(1) a')?.textContent; expect(secondKeywordTitle).toBe('image compressor'); }); diff --git a/__tests__/pages/domains.test.tsx b/__tests__/pages/domains.test.tsx new file mode 100644 index 0000000..f082746 --- /dev/null +++ b/__tests__/pages/domains.test.tsx @@ -0,0 +1,49 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import * as ReactQuery from 'react-query'; +import { dummyDomain } from '../../__mocks__/data'; +import Domains from '../../pages/domains'; + +jest.mock('next/router', () => jest.requireActual('next-router-mock')); +jest.spyOn(ReactQuery, 'useQuery').mockImplementation(jest.fn().mockReturnValue( + { data: { domains: [dummyDomain] }, isLoading: false, isSuccess: true }, +)); + +fetchMock.mockIf(`${window.location.origin}/api/domains`, async () => { + return new Promise((resolve) => { + resolve({ + body: JSON.stringify({ domains: [dummyDomain] }), + status: 200, + }); + }); +}); + +describe('Domains Page', () => { + const queryClient = new QueryClient(); + it('Renders without crashing', async () => { + render( + + + , + ); + expect(screen.getByTestId('domains')).toBeInTheDocument(); + }); + it('Renders the Domain Component', async () => { + const { container } = render( + + + , + ); + expect(container.querySelector('.domItem')).toBeInTheDocument(); + }); + it('Should Display Add Domain Modal on relveant Button Click.', async () => { + render(); + const button = screen.getByTestId('addDomainButton'); + if (button) fireEvent.click(button); + expect(screen.getByTestId('adddomain_modal')).toBeVisible(); + }); + it('Should Display the version number in Footer.', async () => { + render(); + expect(screen.getByText('SerpBear v0.0.0')).toBeVisible(); + }); +}); diff --git a/__test__/pages/index.test.tsx b/__tests__/pages/index.test.tsx similarity index 57% rename from __test__/pages/index.test.tsx rename to __tests__/pages/index.test.tsx index d8f0f58..7819a59 100644 --- a/__test__/pages/index.test.tsx +++ b/__tests__/pages/index.test.tsx @@ -2,21 +2,16 @@ import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import Home from '../../pages/index'; +const routerPush = jest.fn(); +jest.mock('next/router', () => ({ + useRouter: () => ({ + push: routerPush, + }), +})); + describe('Home Page', () => { const queryClient = new QueryClient(); it('Renders without crashing', async () => { - // const dummyDomain = { - // ID: 1, - // domain: 'compressimage.io', - // slug: 'compressimage-io', - // keywordCount: 0, - // lastUpdated: '2022-11-11T10:00:32.243', - // added: '2022-11-11T10:00:32.244', - // tags: [], - // notification: true, - // notification_interval: 'daily', - // notification_emails: '', - // }; render( @@ -26,12 +21,12 @@ describe('Home Page', () => { expect(await screen.findByRole('main')).toBeInTheDocument(); expect(screen.queryByText('Add Domain')).not.toBeInTheDocument(); }); - it('Should Display the Add Domain Modal when there are no Domains.', async () => { + it('Should redirect to /domains route.', async () => { render( , ); - expect(await screen.findByText('Add Domain')).toBeInTheDocument(); + expect(routerPush).toHaveBeenCalledWith('/domains'); }); }); diff --git a/components/settings/Settings.tsx b/components/settings/Settings.tsx index b53eb0e..a15b5e1 100644 --- a/components/settings/Settings.tsx +++ b/components/settings/Settings.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Toaster } from 'react-hot-toast'; -import useUpdateSettings, { useFetchSettings } from '../../services/settings'; +import { useFetchSettings, useUpdateSettings } from '../../services/settings'; import Icon from '../common/Icon'; import NotificationSettings from './NotificationSettings'; import ScraperSettings from './ScraperSettings'; diff --git a/jest.setup.js b/jest.setup.js index 66e0e7f..b18c44f 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,11 +1,26 @@ // eslint-disable-next-line no-unused-vars import 'isomorphic-fetch'; import './styles/globals.css'; +import '@testing-library/jest-dom'; +import { enableFetchMocks } from 'jest-fetch-mock'; // Optional: configure or set up a testing framework before each test. // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` // Used for __tests__/testing-library.js // Learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; + +window.matchMedia = (query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), +}); global.ResizeObserver = require('resize-observer-polyfill'); + +// Enable Fetch Mocking +enableFetchMocks(); diff --git a/package-lock.json b/package-lock.json index 82ddcd8..3390963 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,8 @@ "eslint-config-next": "12.3.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-fetch-mock": "^3.0.3", + "next-router-mock": "^0.9.10", "postcss": "^8.4.31", "prettier": "^2.7.1", "resize-observer-polyfill": "^1.5.1", @@ -4152,6 +4154,15 @@ "node": ">=6.0" } }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7829,6 +7840,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -9840,6 +9861,16 @@ } } }, + "node_modules/next-router-mock": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/next-router-mock/-/next-router-mock-0.9.10.tgz", + "integrity": "sha512-bK6sRb/xGNFgHVUZuvuApn6KJBAKTPiP36A7a9mO77U4xQO5ukJx9WHlU67Tv8AuySd09pk0+Hu8qMVIAmLO6A==", + "dev": true, + "peerDependencies": { + "next": ">=10.0.0", + "react": ">=17.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", @@ -10835,6 +10866,12 @@ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "optional": true }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", diff --git a/package.json b/package.json index 6e07b09..3728243 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,8 @@ "eslint-config-next": "12.3.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-fetch-mock": "^3.0.3", + "next-router-mock": "^0.9.10", "postcss": "^8.4.31", "prettier": "^2.7.1", "resize-observer-polyfill": "^1.5.1", diff --git a/pages/domains/index.tsx b/pages/domains/index.tsx index b2aad58..489c406 100644 --- a/pages/domains/index.tsx +++ b/pages/domains/index.tsx @@ -14,7 +14,7 @@ import Icon from '../../components/common/Icon'; type thumbImages = { [domain:string] : string } -const SingleDomain: NextPage = () => { +const Domains: NextPage = () => { const router = useRouter(); const [noScrapprtError, setNoScrapprtError] = useState(false); const [showSettings, setShowSettings] = useState(false); @@ -66,7 +66,7 @@ const SingleDomain: NextPage = () => { }; return ( -
+
{noScrapprtError && (
A Scrapper/Proxy has not been set up Yet. Open Settings to set it up and start using the app. @@ -84,6 +84,7 @@ const SingleDomain: NextPage = () => {