From 7d24fcfd4ba937be81a841ed27a3049823b31fe6 Mon Sep 17 00:00:00 2001 From: Sudhanshu Gautam Date: Wed, 13 Jan 2021 18:41:12 +0530 Subject: [PATCH] add more tests. Push code coverage to codecov --- .github/workflows/main.yml | 5 +- src/components/Header.test.tsx | 60 +++++++++ src/components/Header.tsx | 9 +- .../home/components/ProfileDetails.test.tsx | 60 +++++++-- .../home/components/ProfileDetails.tsx | 23 ++-- .../home/components/ProfileSearch.test.tsx | 118 ++++++++++++++++++ .../{SearchField.tsx => ProfileSearch.tsx} | 18 +-- .../home/components/VersionSelector.test.tsx | 46 +++++++ .../home/components/VersionSelector.tsx | 1 + src/containers/home/home.test.tsx | 31 +++++ src/containers/home/home.tsx | 19 ++- src/containers/home/utils/title.test.ts | 29 +++++ src/containers/home/utils/title.ts | 6 + src/types/overview.ts | 4 +- src/types/profile.ts | 4 +- 15 files changed, 382 insertions(+), 51 deletions(-) create mode 100644 src/components/Header.test.tsx create mode 100644 src/containers/home/components/ProfileSearch.test.tsx rename src/containers/home/components/{SearchField.tsx => ProfileSearch.tsx} (84%) create mode 100644 src/containers/home/components/VersionSelector.test.tsx create mode 100644 src/containers/home/home.test.tsx create mode 100644 src/containers/home/utils/title.test.ts create mode 100644 src/containers/home/utils/title.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a80f994..85abd2b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,4 +38,7 @@ jobs: run: yarn lint - name: Test 🔧 - run: yarn test + run: yarn test -- --coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 diff --git a/src/components/Header.test.tsx b/src/components/Header.test.tsx new file mode 100644 index 0000000..c50e4ad --- /dev/null +++ b/src/components/Header.test.tsx @@ -0,0 +1,60 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-restricted-syntax */ +import React from 'react'; +import { fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; + +import Header from './Header'; + +const mockChangeLanguage = jest.fn(); + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (k: string) => k, + i18n: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + changeLanguage: mockChangeLanguage, + language: undefined, + }, + }), +})); + +describe('Header', () => { + it('opens the language menu list when the button is clicked', async () => { + const { getByTestId } = render(
); + + const languageMenuToggle = getByTestId('language-menu-toggle'); + expect(languageMenuToggle).toBeInTheDocument(); + + fireEvent.click(languageMenuToggle); + + expect(getByTestId('locale-en')).toBeInTheDocument(); + }); + + describe('with open language menu', () => { + let component: RenderResult | null; + + beforeEach(() => { + component = render(
); + const languageMenuToggle = component.getByTestId('language-menu-toggle'); + fireEvent.click(languageMenuToggle); + }); + + it('changes the language when an item is clicked', () => { + fireEvent.click(component!.getByTestId('locale-en')); + + expect(mockChangeLanguage).toBeCalledWith('en'); + }); + + it('closes the menu with escape', async () => { + fireEvent.keyDown(component!.getByTestId('language-menu'), { key: 'Escape' }); + + await waitFor(() => + expect( + component!.container.querySelector('[data-testid="locale-en"]') + ).not.toBeInTheDocument() + ); + }); + }); +}); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 580f544..73311de 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -37,6 +37,7 @@ const Header: FunctionComponent = () => {
toggleLanguageSwitch(false)} > {Object.keys(locales).map((l) => ( - handleLanguageChange(l)}> + handleLanguageChange(l)} + data-testid={`locale-${l}`} + > {t(locales[l])} ))} diff --git a/src/containers/home/components/ProfileDetails.test.tsx b/src/containers/home/components/ProfileDetails.test.tsx index 9104839..def1dce 100644 --- a/src/containers/home/components/ProfileDetails.test.tsx +++ b/src/containers/home/components/ProfileDetails.test.tsx @@ -38,18 +38,58 @@ const testProfile1: Profile = { const testProfile2: Profile = { build_at: '2020-12-08 13:51:01', - target: 'TEST_TARGET', - version_code: 'TEST_VERSION_CODE', - version_number: 'TEST_VERSION_NUMBER', - id: 'TEST_ID', + target: 'TEST_TARGET_2', + version_code: 'TEST_VERSION_CODE_2', + version_number: 'TEST_VERSION_NUMBER_2', + id: 'TEST_ID_2', titles: [ { title: 'TEST_TITLE2', }, ], + images: [ + { + name: 'factorytestimage', + type: 'factory', + sha256: 'sha256', + }, + { + name: 'kerneltestimage', + type: 'kernel', + sha256: 'sha256', + }, + { + name: 'roottestimage', + type: 'root', + sha256: 'sha256', + }, + { + name: 'tftptestimage', + type: 'tftp', + sha256: 'sha256', + }, + { + name: 'sdcardtestimage', + type: 'sdcard', + sha256: 'sha256', + }, + { + name: 'randomtestimage', + type: 'random', + sha256: 'sha256', + }, + ], }; -const testProfiles = [testProfile1, testProfile2]; +const testProfile3: Profile = { + build_at: '2020-12-08 13:51:01', + target: 'TEST_TARGET_3', + version_code: 'TEST_VERSION_CODE_3', + version_number: 'TEST_VERSION_NUMBER_3', + id: 'TEST_ID_3', +}; + +const testProfiles = [testProfile1, testProfile2, testProfile3]; jest.mock('react-i18next', () => ({ useTranslation: () => ({ @@ -67,7 +107,7 @@ describe('Profile Details', () => { mockAxios.reset(); }); - it('renders the component, sends a get request and displays and shows the profile target', async () => { + it('renders the component and sends a get request', async () => { render( { it('renders download links correctly', async () => { for (const p of testProfiles) { - render( + const { getAllByTestId } = render( @@ -136,7 +176,7 @@ describe('Profile Details', () => { mockAxios.onGet().replyOnce(200, p); await waitFor(() => { - const downloadLinks = screen.getAllByTestId('download_link'); + const downloadLinks = getAllByTestId('download_link'); let expectedItems = p.images?.map((i) => i.type) || []; downloadLinks.forEach((downloadLink) => { expectedItems = expectedItems.filter((i) => i !== downloadLink.textContent); diff --git a/src/containers/home/components/ProfileDetails.tsx b/src/containers/home/components/ProfileDetails.tsx index 86a49b4..6805bd4 100644 --- a/src/containers/home/components/ProfileDetails.tsx +++ b/src/containers/home/components/ProfileDetails.tsx @@ -20,16 +20,13 @@ import { useTranslation } from 'react-i18next'; import { ProfilesEntity } from '../../../types/overview'; import { Profile, TitlesEntity } from '../../../types/profile'; import config from '../../../config'; +import { getTitle } from '../utils/title'; type Props = { selectedVersion: string; selectedProfile: ProfilesEntity; }; -const getTitle = (title: TitlesEntity) => { - return title.title || `${title.vendor} ${title.model}`; -}; - const profilesData: { [key: string]: Profile } = {}; const ProfileDetails: FunctionComponent = ({ selectedVersion, selectedProfile }) => { @@ -75,11 +72,9 @@ const ProfileDetails: FunctionComponent = ({ selectedVersion, selectedPro useEffect(() => { let mounted = true; - if (selectedVersion && selectedProfile) { - getProfileData().then((_profileData) => { - if (mounted && !isEqual(profile, _profileData)) setProfileData(_profileData); - }); - } + getProfileData().then((_profileData) => { + if (mounted && !isEqual(profile, _profileData)) setProfileData(_profileData); + }); return () => { mounted = false; @@ -143,14 +138,10 @@ const ProfileDetails: FunctionComponent = ({ selectedVersion, selectedPro ); }) - .reduce((acc: ReactNode, curr: ReactNode) => [ + .reduce((acc: ReactNode, curr: ReactNode, i: number) => [ acc, // eslint-disable-next-line react/no-array-index-key - , + , curr, ])} @@ -177,7 +168,7 @@ const ProfileDetails: FunctionComponent = ({ selectedVersion, selectedPro .replace('{target}', profile.target) .replace('{version}', profile.version_number)}/${i.name}`; return ( - +