Skip to content

Commit

Permalink
fix(app-platform): upgrade platform tools to use vite and react 18
Browse files Browse the repository at this point in the history
  • Loading branch information
kabaros committed Oct 22, 2024
1 parent 2be4e6e commit 61a29fb
Show file tree
Hide file tree
Showing 34 changed files with 3,559 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
![React 18](https://img.shields.io/badge/react-18-blue)
[![codecov](https://codecov.io/gh/dhis2/login-app/graph/badge.svg?token=3RL8FV6K0L)](https://codecov.io/gh/dhis2/login-app)

This project was bootstrapped with [DHIS2 Application Platform](https://github.com/dhis2/app-platform).
Expand Down
125 changes: 125 additions & 0 deletions src/app.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { CssReset, CssVariables } from '@dhis2/ui'
import parse from 'html-react-parser'
import React from 'react'
import { HashRouter, Navigate, Routes, Route } from 'react-router-dom'
import {
ApplicationDescription,
ApplicationLeftFooter,
ApplicationRightFooter,
ApplicationTitle,
Flag,
LanguageSelect,
Logo,
PoweredByDHIS2,
} from './components/customizable-elements.jsx'
import { Popup } from './components/pop-up.jsx'
import { sanitizeMainHTML } from './helpers/handleHTML.js'
import {
LoginPage,
CompleteRegistrationPage,
CreateAccountPage,
PasswordResetRequestPage,
PasswordUpdatePage,
SafeModePage,
DownloadPage,
} from './pages/index.js'
import { LoginConfigProvider, useLoginConfig } from './providers/index.js'
import i18n from './locales/index.js' // eslint-disable-line
import { standard, sidebar } from './templates/index.js'

const LoginRoutes = () => {
return (
<>
<Popup />
<Routes>
<Route path="/" element={<LoginPage />} />
<Route path="/create-account" element={<CreateAccountPage />} />
<Route
path="/complete-registration"
element={<CompleteRegistrationPage />}
/>
<Route
path="/reset-password"
element={<PasswordResetRequestPage />}
/>
<Route
path="/update-password"
element={<PasswordUpdatePage />}
/>
<Route path="/safeMode" element={<SafeModePage />} />
<Route path="/download" element={<DownloadPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</>
)
}

const options = {
replace: ({ attribs }) => {
if (!attribs) {
return
}

if (attribs.id === 'login-box') {
return <LoginRoutes />
}

if (attribs.id === 'application-title') {
return <ApplicationTitle />
}

if (attribs.id === 'application-introduction') {
return <ApplicationDescription />
}

if (attribs.id === 'flag') {
return <Flag />
}

if (attribs.id === 'logo') {
return <Logo />
}

if (attribs.id === 'powered-by') {
return <PoweredByDHIS2 />
}

if (attribs.id === 'application-left-footer') {
return <ApplicationLeftFooter />
}

if (attribs.id === 'application-right-footer') {
return <ApplicationRightFooter />
}

if (attribs.id === 'language-select') {
return <LanguageSelect />
}
},
}

export const AppContent = () => {
const { loginPageLayout, loginPageTemplate } = useLoginConfig()
let html
if (loginPageLayout === 'SIDEBAR') {
html = sidebar
} else if (loginPageLayout === 'CUSTOM') {
html = loginPageTemplate ?? standard
} else {
html = standard
}

return <>{parse(sanitizeMainHTML(html), options)}</>
}

const App = () => (
<HashRouter>
<LoginConfigProvider initialLocation={window?.location?.href}>
<CssReset />
<CssVariables colors spacers theme elevations />
<AppContent />
</LoginConfigProvider>
</HashRouter>
)

export default App
154 changes: 154 additions & 0 deletions src/app.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { screen } from '@testing-library/react'
import React from 'react'
import { AppContent } from './app.jsx'
import { useLoginConfig } from './providers/use-login-config.js'
import { renderWithRouter } from './test-utils/index.js'

jest.mock('./components/customizable-elements.js', () => ({
...jest.requireActual('./components/customizable-elements.js'),
LanguageSelect: () => <div>MOCK_LANGUAGE_SELECT</div>,
ApplicationTitle: () => <div>MOCK_APPLICATION_TITLE</div>,
ApplicationDescription: () => <div>MOCK_APPLICATION_DESCRIPTION</div>,
Flag: () => <div>MOCK_FLAG</div>,
Logo: () => <div>MOCK_LOGO</div>,
ApplicationLeftFooter: () => <div>MOCK_APPLICATION_LEFT_FOOTER</div>,
ApplicationRightFooter: () => <div>MOCK_APPLICATION_RIGHT_FOOTER</div>,
PoweredByDHIS2: () => <div>MOCK_POWERED_BY</div>,
}))

jest.mock('./providers/use-login-config.js', () => ({
useLoginConfig: jest.fn(() => ({
loginPageLayout: 'DEFAULT',
loginPageTemplate: null,
})),
}))

jest.mock('./templates/index.js', () => ({
__esModule: true,
standard: '<div>STANDARD TEMPLATE</div>',
sidebar: '<div>SIDEBAR TEMPLATE</div>',
}))

describe('AppContent', () => {
afterEach(() => {
jest.clearAllMocks()
})

it('loads standard template if loginPageLayout is DEFAULT', () => {
renderWithRouter(<AppContent />)
expect(screen.getByText('STANDARD TEMPLATE')).toBeInTheDocument()
})

it('loads sidebar template if loginPageLayout is SIDEBAR', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'SIDEBAR',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('SIDEBAR TEMPLATE')).toBeInTheDocument()
})

it('loads standard template if loginPageLayout is CUSTOM and loginPageTemplate is null', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: null,
})
renderWithRouter(<AppContent />)
expect(screen.getByText('STANDARD TEMPLATE')).toBeInTheDocument()
})

it('loads custom loginPageTemplate if loginPageLayout is CUSTOM and loginPageTemplate is not null', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div>CUSTOM TEMPLATE</div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('CUSTOM TEMPLATE')).toBeInTheDocument()
})

it('replaces application-title element with ApplicationTitle component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-title"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_APPLICATION_TITLE')).toBeInTheDocument()
})

it('replaces application-introduction element with ApplicationDescription component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-introduction"></div>',
})
renderWithRouter(<AppContent />)
expect(
screen.getByText('MOCK_APPLICATION_DESCRIPTION')
).toBeInTheDocument()
})

it('replaces application-title element with ApplicationDescription component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-title"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_APPLICATION_TITLE')).toBeInTheDocument()
})

it('replaces application-title element with ApplicationDescription component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-title"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_APPLICATION_TITLE')).toBeInTheDocument()
})

it('replaces flag element with Flag component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="flag"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_FLAG')).toBeInTheDocument()
})

it('replaces logo element with Logo component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="logo"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_LOGO')).toBeInTheDocument()
})

it('replaces powered-by element with PoweredBy component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="powered-by"></div>',
})
renderWithRouter(<AppContent />)
expect(screen.getByText('MOCK_POWERED_BY')).toBeInTheDocument()
})

it('replaces application-left-footer element with ApplicationLeftFooter component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-left-footer"></div>',
})
renderWithRouter(<AppContent />)
expect(
screen.getByText('MOCK_APPLICATION_LEFT_FOOTER')
).toBeInTheDocument()
})

it('replaces application-right-footer element with ApplicationRightFooter component ', () => {
useLoginConfig.mockReturnValue({
loginPageLayout: 'CUSTOM',
loginPageTemplate: '<div id="application-right-footer"></div>',
})
renderWithRouter(<AppContent />)
expect(
screen.getByText('MOCK_APPLICATION_RIGHT_FOOTER')
).toBeInTheDocument()
})
})
48 changes: 48 additions & 0 deletions src/components/__tests__/back-to-login-button.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, screen, fireEvent } from '@testing-library/react'
import PropTypes from 'prop-types'
import React from 'react'
import { MemoryRouter, Route, Routes } from 'react-router-dom'
import { BackToLoginButton } from '../back-to-login-button.jsx'

const MainPage = () => <div>MAIN PAGE</div>
const OtherPage = ({ children }) => <>{children}</>

OtherPage.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
}

const Wrapper = ({ children }) => (
<MemoryRouter initialEntries={['/other']}>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/other" element={<OtherPage>{children}</OtherPage>} />
</Routes>
</MemoryRouter>
)

Wrapper.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
}

describe('BackToLoginButton', () => {
it('redirects to main page when clicked', () => {
render(
<Wrapper>
<BackToLoginButton />
</Wrapper>
)
expect(screen.queryByText('MAIN PAGE')).toBe(null)
fireEvent.click(
screen.getByRole('button', {
name: /back to log in/i,
})
)
expect(screen.getByText('MAIN PAGE')).not.toBe(null)
})
})
Loading

0 comments on commit 61a29fb

Please sign in to comment.