1
1
mirror of https://github.com/theoludwig/theoludwig.git synced 2024-11-08 22:31:30 +01:00

test: add cypress e2e (#159)

This commit is contained in:
Divlo 2021-08-13 15:48:29 +02:00 committed by GitHub
parent f7d304ca80
commit 266b3f8589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2893 additions and 1373 deletions

View File

@ -46,7 +46,24 @@ jobs:
- run: 'npm run lint:markdown' - run: 'npm run lint:markdown'
- run: 'npm run lint:typescript' - run: 'npm run lint:typescript'
build: test-unit:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.4.0'
with:
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Unit Test'
run: 'npm run test:unit'
test-lighthouse:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v2.3.4' - uses: 'actions/checkout@v2.3.4'
@ -64,11 +81,11 @@ jobs:
run: 'npm run build' run: 'npm run build'
- name: 'Lighthouse' - name: 'Lighthouse'
run: 'npm run lighthouse' run: 'npm run test:lighthouse'
env: env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
test: test-e2e:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v2.3.4' - uses: 'actions/checkout@v2.3.4'
@ -82,12 +99,15 @@ jobs:
- name: 'Install' - name: 'Install'
run: 'npm install' run: 'npm install'
- name: 'Test' - name: 'Build'
run: 'npm run test' run: 'npm run build'
- name: 'End To End (e2e) Test'
run: 'npm run test:e2e'
release: release:
if: github.ref == 'refs/heads/master' && github.event_name == 'push' if: github.ref == 'refs/heads/master' && github.event_name == 'push'
needs: [analyze, lint, build, test] needs: [analyze, lint, test-unit, test-e2e, test-lighthouse]
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v2.3.4' - uses: 'actions/checkout@v2.3.4'

3
.gitignore vendored
View File

@ -14,6 +14,9 @@ dist
# testing # testing
coverage coverage
cypress/screenshots
cypress/videos
cypress/downloads
# PWA # PWA
**/workbox-*.js **/workbox-*.js

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import Error404 from 'pages/404'
describe('GET /404', () => {
it('should render', async () => {
const { getByText } = render(<Error404 />)
expect(getByText('404')).toBeInTheDocument()
})
})

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import Error500 from 'pages/500'
describe('GET /500', () => {
it('should render', async () => {
const { getByText } = render(<Error500 />)
expect(getByText('500')).toBeInTheDocument()
})
})

View File

@ -14,7 +14,12 @@ export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
<> <>
<h1 className='my-6 font-semibold text-4xl'> <h1 className='my-6 font-semibold text-4xl'>
{t('errors:error')}{' '} {t('errors:error')}{' '}
<span className='text-yellow dark:text-yellow-dark'>{statusCode}</span> <span
className='text-yellow dark:text-yellow-dark'
data-cy='status-code'
>
{statusCode}
</span>
</h1> </h1>
<p className='text-center text-lg'> <p className='text-center text-lg'>
{message}{' '} {message}{' '}

View File

@ -15,7 +15,9 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
src={`/images/languages/${language}.svg`} src={`/images/languages/${language}.svg`}
alt={language} alt={language}
/> />
<p className='mx-2 text-base'>{language.toUpperCase()}</p> <p data-cy='language-flag-text' className='mx-2 text-base'>
{language.toUpperCase()}
</p>
</> </>
) )
} }

View File

@ -33,12 +33,19 @@ export const Language: React.FC = () => {
return ( return (
<div className='flex flex-col justify-center items-center cursor-pointer'> <div className='flex flex-col justify-center items-center cursor-pointer'>
<div className='flex items-center mr-5' onClick={handleHiddenMenu}> <div
data-cy='language-click'
className='flex items-center mr-5'
onClick={handleHiddenMenu}
>
<LanguageFlag language={currentLanguage} /> <LanguageFlag language={currentLanguage} />
<Arrow /> <Arrow />
</div> </div>
{!hiddenMenu && ( {!hiddenMenu && (
<ul className='flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black'> <ul
data-cy='languages-list'
className='flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black'
>
{i18n.locales.map((language, index) => { {i18n.locales.map((language, index) => {
if (language === currentLanguage) { if (language === currentLanguage) {
return null return null

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes'
export const SwitchTheme: React.FC = () => { export const SwitchTheme: React.FC = () => {
@ -13,23 +13,29 @@ export const SwitchTheme: React.FC = () => {
return null return null
} }
const handleClick = (): void => {
setTheme(theme === 'dark' ? 'light' : 'dark')
}
return ( return (
<> <>
<div <div
className='toggle-button' className='toggle-button'
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} data-cy='switch-theme-click'
onClick={handleClick}
> >
<div className='toggle-theme-button'> <div className='toggle-theme-button'>
<div className='toggle-track'> <div className='toggle-track'>
<div className='toggle-track-check'> <div data-cy='switch-theme-dark' className='toggle-track-check'>
<span className='toggle_Dark'>🌜</span> <span className='toggle_Dark'>🌜</span>
</div> </div>
<div className='toggle-track-x'> <div data-cy='switch-theme-light' className='toggle-track-x'>
<span className='toggle_Light'>🌞</span> <span className='toggle_Light'>🌞</span>
</div> </div>
</div> </div>
<div className='toggle-thumb' /> <div className='toggle-thumb' />
<input <input
data-cy='switch-theme-input'
type='checkbox' type='checkbox'
aria-label='Dark mode toggle' aria-label='Dark mode toggle'
className='toggle-screenreader-only' className='toggle-screenreader-only'

View File

@ -3,10 +3,14 @@ import { render } from '@testing-library/react'
import { Footer } from '../Footer' import { Footer } from '../Footer'
describe('<Footer />', () => { describe('<Footer />', () => {
it('should render', async () => { it('should render with appropriate link tag version', async () => {
const version = '1.0.0' const version = '1.0.0'
const { getByText } = render(<Footer version={version} />) const { getByText } = render(<Footer version={version} />)
const versionLink = getByText(version) as HTMLAnchorElement
expect(getByText('Divlo')).toBeInTheDocument() expect(getByText('Divlo')).toBeInTheDocument()
expect(getByText(version)).toBeInTheDocument() expect(versionLink).toBeInTheDocument()
expect(versionLink.href).toEqual(
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
)
}) })
}) })

8
cypress.json Normal file
View File

@ -0,0 +1,8 @@
{
"baseUrl": "http://localhost:3000",
"pluginsFile": false,
"supportFile": false,
"fixturesFolder": false,
"video": false,
"screenshotOnRunFailure": false
}

View File

@ -0,0 +1,47 @@
describe('Common > Header', () => {
beforeEach(() => cy.visit('/'))
describe('Switch theme color (dark/light)', () => {
it('should switch theme from `dark` (default) to `light`', () => {
cy.get('[data-cy=switch-theme-dark]').should('be.visible')
cy.get('[data-cy=switch-theme-light]').should('not.be.visible')
cy.get('body').should(
'not.have.css',
'background-color',
'rgb(255, 255, 255)'
)
cy.get('[data-cy=switch-theme-click]').click()
cy.get('[data-cy=switch-theme-dark]').should('not.be.visible')
cy.get('[data-cy=switch-theme-light]').should('be.visible')
cy.get('body').should(
'have.css',
'background-color',
'rgb(255, 255, 255)'
)
})
})
describe('Switch Language', () => {
it('should switch language from EN (default) to FR', () => {
cy.get('h1').contains('I am Divlo')
cy.get('[data-cy=language-flag-text]').contains('EN')
cy.get('[data-cy=languages-list]').should('not.exist')
cy.get('[data-cy=language-click]').click()
cy.get('[data-cy=languages-list]').should('exist')
cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click()
cy.get('[data-cy=languages-list]').should('not.exist')
cy.get('[data-cy=language-flag-text]').contains('FR')
cy.get('h1').contains('Je suis Divlo')
})
it('should close the language list menu when clicking outside', () => {
cy.get('[data-cy=languages-list]').should('not.exist')
cy.get('[data-cy=language-click]').click()
cy.get('[data-cy=languages-list]').should('exist')
cy.get('h1').click()
cy.get('[data-cy=languages-list]').should('not.exist')
})
})
})

View File

@ -0,0 +1,7 @@
describe('Page /404', () => {
beforeEach(() => cy.visit('/404', { failOnStatusCode: false }))
it('should display the statusCode of 404', () => {
cy.get('[data-cy=status-code]').contains('404')
})
})

View File

@ -0,0 +1,7 @@
describe('Page /500', () => {
beforeEach(() => cy.visit('/500', { failOnStatusCode: false }))
it('should display the statusCode of 500', () => {
cy.get('[data-cy=status-code]').contains('500')
})
})

View File

@ -0,0 +1,19 @@
describe('Page /', () => {
beforeEach(() => cy.visit('/'))
it('should reveals the sections while scrolling except the about section', () => {
const sectionsReveals = [
'#interests',
'#skills',
'#portfolio',
'#open-source'
]
cy.get('#about').should('be.visible')
for (const section of sectionsReveals) {
cy.get(section)
.should('not.be.visible')
.scrollIntoView()
.should('be.visible')
}
})
})

9
cypress/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["cypress"],
"isolatedModules": false
},
"include": ["../node_modules/cypress", "./**/*.ts"]
}

View File

@ -4,13 +4,11 @@
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest" "^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
}, },
"moduleDirectories": ["node_modules", "./"], "moduleDirectories": ["node_modules", "./"],
"modulePathIgnorePatterns": ["<rootDir>/cypress"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"testEnvironment": "jsdom", "testEnvironment": "jsdom",
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"@testing-library/jest-dom/extend-expect", "@testing-library/jest-dom/extend-expect",
"@testing-library/react" "@testing-library/react"
], ]
"collectCoverage": true,
"coverageDirectory": "./coverage",
"coverageReporters": ["text", "cobertura"]
} }

4039
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,8 +21,10 @@
"lint:markdown": "markdownlint '**/*.md' --dot --ignore node_modules", "lint:markdown": "markdownlint '**/*.md' --dot --ignore node_modules",
"lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'", "lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"lighthouse": "lhci autorun", "test:unit": "jest",
"test": "jest", "test:lighthouse": "lhci autorun",
"test:e2e": "start-server-and-test 'start' 'http://localhost:3000' 'cypress run'",
"test:e2e:dev": "start-server-and-test 'dev' 'http://localhost:3000' 'cypress open'",
"release": "semantic-release", "release": "semantic-release",
"deploy": "vercel", "deploy": "vercel",
"postinstall": "husky install" "postinstall": "husky install"
@ -34,7 +36,7 @@
"@fortawesome/free-solid-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.15", "@fortawesome/react-fontawesome": "0.1.15",
"classnames": "2.3.1", "classnames": "2.3.1",
"html-react-parser": "1.2.7", "html-react-parser": "1.2.8",
"next": "11.1.0", "next": "11.1.0",
"next-pwa": "5.2.24", "next-pwa": "5.2.24",
"next-themes": "0.0.15", "next-themes": "0.0.15",
@ -53,13 +55,14 @@
"@semantic-release/git": "9.0.0", "@semantic-release/git": "9.0.0",
"@testing-library/jest-dom": "5.14.1", "@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "12.0.0", "@testing-library/react": "12.0.0",
"@types/jest": "27.0.0", "@types/jest": "27.0.1",
"@types/node": "16.6.0", "@types/node": "16.6.1",
"@types/react": "17.0.17", "@types/react": "17.0.17",
"@types/styled-jsx": "2.2.9", "@types/styled-jsx": "2.2.9",
"@typescript-eslint/eslint-plugin": "4.29.1", "@typescript-eslint/eslint-plugin": "4.29.1",
"autoprefixer": "10.3.1", "autoprefixer": "10.3.1",
"babel-jest": "27.0.6", "babel-jest": "27.0.6",
"cypress": "8.2.0",
"dockerfilelint": "1.8.0", "dockerfilelint": "1.8.0",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"eslint": "7.32.0", "eslint": "7.32.0",
@ -79,6 +82,7 @@
"postcss": "8.3.6", "postcss": "8.3.6",
"prettier": "2.3.2", "prettier": "2.3.2",
"semantic-release": "17.4.4", "semantic-release": "17.4.4",
"start-server-and-test": "1.13.1",
"tailwindcss": "2.2.7", "tailwindcss": "2.2.7",
"typescript": "4.3.5", "typescript": "4.3.5",
"vercel": "23.1.2" "vercel": "23.1.2"

View File

@ -10,6 +10,7 @@
"removeComments": true, "removeComments": true,
"noEmit": true, "noEmit": true,
"strict": true, "strict": true,
"types": ["jest", "@testing-library/jest-dom", "@testing-library/react"],
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,