Skip to content

Commit

Permalink
test: add ui tests with rtl, jest and msw
Browse files Browse the repository at this point in the history
  • Loading branch information
kabaros committed Jan 16, 2025
1 parent 05d8224 commit ca28a57
Show file tree
Hide file tree
Showing 21 changed files with 5,589 additions and 1,493 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/dhis2-verify-commits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ jobs:
- run: yarn install --frozen-lockfile
- id: lint
run: yarn lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn test:client --coverage
- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
lint-pr-title:
runs-on: ubuntu-latest
steps:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ build/
cypress.env.json

# ignore tools download cache
tools/apps
tools/apps
coverage/
7 changes: 7 additions & 0 deletions client/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import '@testing-library/jest-dom'
import { configure } from '@testing-library/dom'
const util = require('util')
const { TextEncoder, TextDecoder } = util
Object.assign(global, { TextDecoder, TextEncoder })

configure({ testIdAttribute: 'data-test' })
19 changes: 19 additions & 0 deletions client/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @type {import('jest').Config} */
const config = {
verbose: true,
testEnvironment: 'jest-fixed-jsdom',
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],

moduleNameMapper: {
'@dhis2/app-runtime': '<rootDir>/app-runtime-mock.js',
'react-markdown': '<rootDir>/test/mocks/react-markdown-mock.js',
'@react-hook': '<rootDir>/app-runtime-mock.js',
'\\.(jpg|jpeg|png|gif|webp|svg)$': 'identity-obj-proxy',
'\\.(css|scss)$': 'identity-obj-proxy',
'^src(.*)$': '<rootDir>/src$1',
'^config(.*)$': '<rootDir>/config$1',
'^mocks(.*)$': '<rootDir>/test/mocks$1',
},
}

module.exports = config
10 changes: 8 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"scripts": {
"build": "cross-env NODE_ENV=production webpack --mode production --progress --config ./webpack.config.js",
"start": "cross-env NODE_ENV=development webpack serve",
"test": "cross-env NODE_ENV=test mocha --config test/.mocharc.js",
"test": "cross-env NODE_ENV=test jest",
"format": "yarn format:js && yarn format:text",
"format:staged": "yarn format:js --staged && yarn format:text --staged",
"format:js": "d2-style apply js",
Expand Down Expand Up @@ -50,6 +50,8 @@
"@babel/preset-react": "^7.14.5",
"@babel/register": "^7.15.3",
"@svgr/webpack": "^5.5.0",
"@testing-library/jest-dom": "^6",
"@testing-library/react": "^12",
"babel-loader": "^8.2.2",
"babel-plugin-rewire": "^1.2.0",
"babel-plugin-transform-imports": "^2.0.0",
Expand All @@ -61,9 +63,13 @@
"eslint-import-resolver-webpack": "^0.13.9",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"identity-obj-proxy": "^3.0.0",
"isomorphic-fetch": "^3.0.0",
"jest": "^29.7.0",
"jest-fixed-jsdom": "^0.0.9",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^17.0.0",
"mocha": "^9.1.1",
"msw": "^2.7.0",
"sinon": "^11.1.2",
"sinon-chai": "^3.7.0",
"style-loader": "^3.2.1",
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/AppDescription/AppDescription.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styles from './AppDescription.module.css'

const AppDescription = ({ description, paragraphClassName }) => {
return (
<div className={paragraphClassName}>
<div className={paragraphClassName} data-test="app-description">
<ReactMarkdown
className={styles.markdownDescription}
linkTarget={'_blank'}
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/Versions/ChangeLogViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const ChangeLogViewer = ({ appId, latestVersion }) => {
<div>Show changes included from: </div>
<div className={styles.headerTitle}>
<SingleSelectField
dataTest="select-baseversion"
onChange={({ selected }) => setBaseVersion(selected)}
selected={baseVersion}
dense
Expand All @@ -100,6 +101,7 @@ const ChangeLogViewer = ({ appId, latestVersion }) => {
</div>
<div>to</div>
<SingleSelectField
dataTest="select-compareversion"
onChange={({ selected }) => setCompareVersion(selected)}
selected={versionToCompareWith}
dense
Expand Down
29 changes: 29 additions & 0 deletions client/src/components/Versions/ChangelogViewer.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { render } from '@testing-library/react'
import ChangeLogViewer from './ChangeLogViewer'
import '../../../test/mocks/setup.js'

describe('Changelog viewer', () => {
it('should show the changes', async () => {
const appId = '70e2cbdd-568b-49fb-83fa-c20b3018da1a'

const { findByText, getByTestId } = render(
<ChangeLogViewer appId={appId} latestVersion="99.12.0" />
)
await findByText('Version 99.12.0')
expect(getByTestId('dhis2-uicore-table')).toHaveTextContent(
'Version 99.12.0Feature: enable template download | link' +
'Version 99.11.0Feature: add OIDC Providers into UI | link' +
'Version 99.10.1Fix: language clean up | link' +
'Version 99.10.0Feature: add redirect for safe mode | link' +
'Version 99.9.16Fix: update app name to match the bundled apps in core | link' +
'Version 99.9.15Fix: remove html unescaping | link' +
'Fix: remove html unescaping | link'
)

const baseVersionList = getByTestId('select-baseversion')
expect(baseVersionList).toHaveTextContent('99.12.0')

const compareversionList = getByTestId('select-compareversion')
expect(compareversionList).toHaveTextContent('99.9.15')
})
})
24 changes: 18 additions & 6 deletions client/src/pages/AppView/AppView.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
CircularLoader,
NoticeBox,
Card,
Divider,
Button,
TabBar,
Tab,
Expand Down Expand Up @@ -47,13 +46,14 @@ const HeaderSection = ({
<div className={styles.tagWithIcon}>
<IconUser16 />
<a
data-test="organisation-link"
className={styles.organisationLink}
href={`/organisation/${organisationSlug}/view`}
>
{appDeveloper}
</a>
</div>
<div className={styles.tagWithIcon}>
<div data-test="app-type" className={styles.tagWithIcon}>
<IconTerminalWindow16 />
{appType}
</div>
Expand All @@ -67,7 +67,12 @@ const HeaderSection = ({
</div>
<div>
<div className={styles.topActionButtons}>
<a download href={latestVersion.downloadUrl} tabIndex="-1">
<a
data-test="button-download-latest-version"
download
href={latestVersion.downloadUrl}
tabIndex="-1"
>
<Button primary>Download latest version</Button>
</a>
<Button
Expand All @@ -76,7 +81,10 @@ const HeaderSection = ({
See previous releases
</Button>
</div>
<div className={styles.latestVersionDescription}>
<div
data-test="latest-version-description"
className={styles.latestVersionDescription}
>
<span>
{
config.ui.appChannelToDisplayName[
Expand All @@ -96,7 +104,11 @@ const HeaderSection = ({
</div>
{sourceUrl && (
<>
<a href={sourceUrl} className={styles.sourceUrl}>
<a
data-test="link-source-code"
href={sourceUrl}
className={styles.sourceUrl}
>
Source code
</a>
</>
Expand Down Expand Up @@ -190,7 +202,7 @@ const AppView = ({ match }) => {
latestVersion={latestVersion}
sourceUrl={app.sourceUrl}
/>
<TabBar>
<TabBar dataTest="tabbar-appview">
<Tab
onClick={selectTab('about')}
selected={selectedTab == 'about'}
Expand Down
58 changes: 58 additions & 0 deletions client/src/pages/AppView/AppView.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { render } from '@testing-library/react'
import AppView from './AppView.js'
import '../../../test/mocks/setup.js'

describe('AppView', () => {
it('should render app details', async () => {
const appId = '08c48425-abd3-410e-8802-8f9ada971c03'

const { findByText, getByTestId } = render(
<AppView
match={{
params: { appId },
}}
/>
)

await findByText('ADEx Flow')

// check organisation link
const organisationLink = getByTestId('organisation-link')
expect(organisationLink).toHaveTextContent(
'DHIS2 Global Implementation Team'
)
expect(organisationLink).toHaveAttribute(
'href',
'/organisation/dhis2-global-implementation-team/view'
)

expect(getByTestId('app-type')).toHaveTextContent('Application')

// Check the buttons
const downloadButton = getByTestId('button-download-latest-version')
expect(downloadButton).toHaveTextContent('Download latest version')
expect(downloadButton.getAttribute('href')).toMatch(
'api/v1/apps/download/dhis2-global-implementation-team/adex-flow_0.1.5.zip'
)

// latest version description
expect(getByTestId('latest-version-description')).toHaveTextContent(
`Stable release v0.1.5.Compatible with DHIS2 2.39 and above.`
)

// app description
expect(getByTestId('app-description')).toHaveTextContent(
'The Global Fund ADEX Flow app provides a series of workflows to assist country teams with the management of Global Fund Related ADEX metadata.The app provides a series of pre-defined workflows:'
)

const sourceCodeLink = getByTestId('link-source-code')
expect(sourceCodeLink.getAttribute('href')).toEqual(
'https://github.com/dhis2/gf-adex-flow-app'
)
expect(sourceCodeLink).toHaveTextContent('Source code')

expect(getByTestId('tabbar-appview')).toHaveTextContent(
'About' + 'Previous releases' + 'Change log'
)
})
})
22 changes: 15 additions & 7 deletions client/src/pages/Apps/AppCards/AppCardItem/AppCardItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ const AppCardItem = ({
const logo = images.find((elem) => elem.logo)

return (
<Link to={`/app/${id}`} className={styles.appCard}>
<Link data-test="appcard" to={`/app/${id}`} className={styles.appCard}>
<header className={styles.appCardHeader}>
<AppIcon src={logo?.imageUrl} />

<div>
<h2 className={styles.appCardName}>{name}</h2>
<span className={styles.appCardDeveloper}>
<h2 data-test="appcard-name" className={styles.appCardName}>
{name}
</h2>
<span
data-test="appcard-organisation"
className={styles.appCardDeveloper}
>
{developer.organisation || 'Unspecified'}
</span>
<div className={styles.appTypeContainer}>
Expand All @@ -44,7 +49,10 @@ const AppCardItem = ({
</div>
</header>

<p className={styles.appCardDescription}>
<p
data-test="appcard-description"
className={styles.appCardDescription}
>
<Summary>
<ReactMarkdown
allowedElements={['p']}
Expand All @@ -67,17 +75,17 @@ Summary.propTypes = {
}

AppCardItem.propTypes = {
appType: PropTypes.string.isRequired,
developer: PropTypes.shape({
name: PropTypes.string,
organisation: PropTypes.string,
}).isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
hasPlugin: PropTypes.bool.isRequired,
pluginType: PropTypes.string.isRequired,
appType: PropTypes.string.isRequired,
description: PropTypes.string,
hasPlugin: PropTypes.bool,
images: PropTypes.array,
pluginType: PropTypes.string,
}

export default AppCardItem
28 changes: 28 additions & 0 deletions client/src/pages/Apps/Apps.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { render } from '@testing-library/react'
import '../../../test/mocks/setup.js'
import { Router, Route } from 'react-router-dom'
import { QueryParamProvider } from 'use-query-params'
import { history } from '../../utils/history'
import Apps from './Apps.js'

describe('Apps list (Homepage)', () => {
it('should render all app cards', async () => {
const { findByText, getAllByTestId } = render(
<Router history={history}>
<QueryParamProvider
ReactRouterRoute={Route}
stringifyOptions={{ skipEmptyString: true }}
>
<Apps />
</QueryParamProvider>
</Router>
)

await findByText('Analytics Info')
expect(getAllByTestId('appcard').length).toEqual(24)
expect(getAllByTestId('appcard-name').length).toEqual(24)
expect(getAllByTestId('appcard-organisation').length).toEqual(24)

expect(getAllByTestId('appcard-description').length).toEqual(24)
})
})
12 changes: 6 additions & 6 deletions client/test/changelog/changelog.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('changelog', () => {
it('should parse the log', () => {
const result = new Changelog(changelogContents)

expect(result.data[0]).to.deep.equal({
expect(result.data[0]).toEqual({
version: '100.2.0',
changeSummary: [
{
Expand All @@ -37,20 +37,20 @@ describe('changelog', () => {
const translations = result.data.filter((r) =>
r.changeSummary.find((e) => e.isTranslation)
)
expect(translations).to.have.length(9)
expect(translations).toHaveLength(9)
})
it('should highlight breaking changes', () => {
const result = new Changelog(changelogContents)
const changes = result.data.filter((r) => r.isBreaking)
expect(changes).to.have.length(1)
expect(changes[0].version).to.equal('100.0.0')
expect(changes).toHaveLength(1)
expect(changes[0].version).toEqual('100.0.0')
})
it('should not fail with invalid changelog', () => {
const result = new Changelog('invalid changelog')
expect(result.data).to.have.length(0)
expect(result.data).toHaveLength(0)
})
it('should not fail with undefine changelog', () => {
const result = new Changelog(undefined)
expect(result.data).to.have.length(0)
expect(result.data).toHaveLength(0)
})
})
Loading

0 comments on commit ca28a57

Please sign in to comment.