Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: setup cypress dependency & environment #322

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# DHIS2 Platform
node_modules
.d2
src/locales
build
cypress.env.json
.vscode
.env.development.local
.env.test.local
.env.production.local
cypress/screenshots/*
10 changes: 10 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"baseUrl": "http://localhost:3000",
"video": false,
"env": {
"dhis2DataTestPrefix": "dhis2-maintenance",
"networkMode": "live",
"dhis2ApiVersion": "40"
},
"experimentalInteractiveRunEvents": true
}
6 changes: 6 additions & 0 deletions cypress/integration/smoke_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
describe('Smoke test', () => {
it('should load the app without any errors', () => {
cy.visitAndLoad('/')
cy.get('div:contains("test")').should('exist')
})
})
9 changes: 9 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const {
networkShim,
chromeAllowXSiteCookies,
} = require('@dhis2/cypress-plugins')

module.exports = (on, config) => {
networkShim(on)
chromeAllowXSiteCookies(on)
}
5 changes: 5 additions & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { enableAutoLogin, enableNetworkShim } from '@dhis2/cypress-commands'
import './visit-and-load.js'

enableAutoLogin()
enableNetworkShim()
4 changes: 4 additions & 0 deletions cypress/support/visit-and-load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Cypress.Commands.add('visitAndLoad', (...args) => {
cy.visit(...args)
cy.get('aside nav').should('exist')
})
9 changes: 9 additions & 0 deletions d2.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const config = {
type: 'app',

entryPoints: {
app: './src/App.tsx',
},
}

module.exports = config
1 change: 1 addition & 0 deletions docs/developer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ has yet to be explored by the entire team).

## Specific topics

* [Setting up & running cypress tests](./cypress-tests.md)
* [Folder structure](./folder-structure.md)
* [Url path structure](./url-path-structure.md)
* [Client-side state](./client-side-state.md)
Expand Down
45 changes: 45 additions & 0 deletions docs/developer/cypress-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Setting up & running cypress tests

## Prerequisites

In order to run cypress tests, a `cypress.env.json` is required in the root
directory. This file is ignored by git on purpose so we don't end up storing
credentials in the public repository.

The file has to look like this:

```json
{
"dhis2BaseUrl": "https://debug.dhis2.org/dev",
"dhis2Username": "system",
"dhis2Password": "System123"
}
```

## Working on cypress tests

The easiest way to work on cypress tests is by starting the app's start script
and the cypress open script independently. There are situations where the
cypress UI has to be restarted and it's more convenient to not have to wait for
the UI as well:

* In one terminal, run `yarn cypress:start`
* In another terminal, run `yarn cypress:open:live`

### Checking whether capturing & stubbing requests work

As this can be a time consuming task, similarly to working on cypress tests,
there are scripts that expect the app to be running already instead of starting
it as well:

* `yarn cypress:run:capture`: Will run the tests and record all network
requests
* `yarn cypress:run:stub`: Will run the tests and use the recorded requests

## Capturing & replaying network requests

When done working on a branch, you can:

* run `yarn cypress:capture` to generate network fixtures
* run `yarn cypress:stub` to test whether the app works with the generated
fixtures
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "maintenance-app",
"version": "1.0.0",
"description": "",
"license": "BSD-3-Clause",
"private": true,
"scripts": {
"build": "d2-app-scripts build",
"start": "d2-app-scripts start",
"start:nobrowser": "BROWSER=none yarn start",
"test": "d2-app-scripts test",
"deploy": "d2-app-scripts deploy",
"cy": "NODE_OPTIONS=--openssl-legacy-provider yarn cypress",
"cypress:start": "REACT_APP_NODE_ENV=test yarn start:nobrowser",
"cypress:open:live": "yarn cy open --env networkMode=live",
"cypress:prepare": "start-server-and-test 'yarn cypress:start' http://localhost:3000",
"cypress:run:live": "yarn cy run --env networkMode=live",
"cypress:live": "yarn cypress:prepare 'yarn cypress:open:live'",
"cypress:run:capture": "yarn cy run --env networkMode=capture",
"cypress:capture": "yarn cypress:prepare 'yarn cypress:run:capture'",
"cypress:run:stub": "yarn cy run --env networkMode=stub",
"cypress:stub": "yarn cypress:prepare 'yarn cypress:run:stub'"
},
"devDependencies": {
"@dhis2/cli-app-scripts": "^10.2.0",
"@dhis2/cypress-commands": "^9.0.2",
"@dhis2/cypress-plugins": "^9.0.2",
"cypress": "^9",
"start-server-and-test": "^1.15.3"
},
"dependencies": {
"@dhis2/app-runtime": "^3.8.0",
"react-router-dom": "^6.8.0"
}
}
9 changes: 9 additions & 0 deletions src/App.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 1rem;
}
15 changes: 15 additions & 0 deletions src/App.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CustomDataProvider } from '@dhis2/app-runtime'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

it('renders without crashing', () => {
const div = document.createElement('div')
ReactDOM.render(
<CustomDataProvider>
<App />
</CustomDataProvider>,
div
)
ReactDOM.unmountComponentAtNode(div)
})
21 changes: 21 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { DataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
// import classes from './App.module.css'
import { Layout, AppWrapper, ConfiguredRouter } from './app/'


const query = {
me: {
resource: 'me',
},
}

const MyApp = () => (
<AppWrapper>
<ConfiguredRouter />
</AppWrapper>

)

export default MyApp
3 changes: 3 additions & 0 deletions src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Layout} from './layout'
export { AppWrapper } from "./wrapper";
export { ConfiguredRouter } from "./router";
1 change: 1 addition & 0 deletions src/app/layout/breakpoints.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@value m-medium: (min-width: 768px);
1 change: 1 addition & 0 deletions src/app/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Layout } from './layout';
31 changes: 31 additions & 0 deletions src/app/layout/layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@value m-medium from './breakpoints.css';

.wrapper {
display: grid;
grid-template-areas: 'sidebar' 'main';
}

@media m-medium {
.wrapper {
height: 100%;
display: grid;
grid-template-columns: 240px auto;
grid-template-areas:
'sidebar main';
}
}


.main {
padding: 4px;
overflow: hidden;
grid-area: main;
}

.sidebar {
padding: 4px;
grid-area: sidebar;
overflow-y: auto;
height: 100%;
background: var(--colors-grey900);
}
28 changes: 28 additions & 0 deletions src/app/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

// create layout component with sidebar and content
import classnames from 'classnames'
import { node, bool } from 'prop-types'
import React from 'react'
import css from './layout.module.css'

interface LayoutProps {
children: React.ReactNode
sidebar: React.ReactNode
}


export const Layout = ({ children, sidebar }: LayoutProps) => {

return (
<div className={css.wrapper}>
<aside className={css.sidebar}>{sidebar}</aside>
<div
className={css.main}
>
{children}
</div>
</div>
);
}

export default Layout
48 changes: 48 additions & 0 deletions src/app/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
createHashRouter,
RouterProvider,
Outlet,
useRouteError,
Navigate,
} from "react-router-dom";
import { Layout } from "./layout";
import { Sidebar } from "./sidebar";
import React, { Suspense, lazy } from "react";
import { CircularLoader } from "@dhis2/ui";
import { routes } from '../pages/'

const DefaultLazyLoad = ({ element }) => (
<Suspense fallback={<CircularLoader />}>{element}</Suspense>
);

// React.lazy only works with default exports, so we import directly instead of from index.tsx
const AllOverview = lazy(() => import("../pages/overview/all-overview"));
//const DataElements = lazy(() => import("../pages/data-elements/data-elements"));

const router = createHashRouter([
{
// no path means its a layout route
element: (
<Layout sidebar={<Sidebar />}>
<Outlet />
</Layout>
),

// TODO: Should we list all routes here, or import children-routes from pages?
// children: [
// {
// path: "/",
// element: <DefaultLazyLoad element={<AllOverview />} />,
// },
// {
// path: 'dataElements',
// element: <div>Data Elements</div>,
// },
// ],
children: routes,
},
]);

export const ConfiguredRouter = () => {
return <RouterProvider router={router} />;
};
1 change: 1 addition & 0 deletions src/app/sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Sidebar } from './sidebar';
3 changes: 3 additions & 0 deletions src/app/sidebar/sidebar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

.sidebar {
}
74 changes: 74 additions & 0 deletions src/app/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import classnames from "classnames";
import React from "react";
import css from "./sidebar.module.css";
import { Sidenav, SidenavItems, SidenavParent, SidenavLink } from "./sidenav/sidenav";
import { NavLink, Link } from "react-router-dom";
interface SidebarProps {
children?: React.ReactNode;
}

export const Sidebar = ({ children }: SidebarProps) => {
const ContentList = [
"Data Elements",
"Categories",
"Organisation Units",
"Data Sets",
"Programs",
].map((item, index) => {
return <li key={index}>{item}</li>;
});
return (
<aside className={css.sidebar}>
<Sidenav>
<SidenavItems>
<SidenavLink><Link to={'/'}>Metadata Overview</Link></SidenavLink>
<SidenavParent label="Categories">
<SidenavLink>Category Option</SidenavLink>
<SidenavLink disabled={true}>Category combination</SidenavLink>
<SidenavLink>Category option combination</SidenavLink>
<SidenavLink><NavLink to={'/hello'}>Hello</NavLink></SidenavLink>
</SidenavParent>
<SidenavParent label="Data elements">
<SidenavLink label="Data element" />
<SidenavLink label="Data element group" />
<SidenavLink label="Data element group set" />
</SidenavParent>
<SidenavParent label="Data sets">
<SidenavLink label="Data set" />
<SidenavLink label="Data set notifications" />
</SidenavParent>
<SidenavParent label="Indicators">
<SidenavLink label="Indicator" />
<SidenavLink label="Indicator type" />
<SidenavLink label="Indicator group" />
<SidenavLink label="Indicator group set" />
<SidenavLink label="Program indicator" />
<SidenavLink label="Program indicator group" />
</SidenavParent>
<SidenavParent label="Organisation units">
<SidenavLink label="Organisation unit" />
<SidenavLink label="Organisation unit group" />
<SidenavLink label="Organisation unit group set" />
<SidenavLink label="Organisation unit level" />
</SidenavParent>
<SidenavParent label="Programs and Tracker">
<SidenavLink label="Program" />
<SidenavLink label="Tracked entity attribute" />
<SidenavLink label="Relationship type" />
<SidenavLink label="Tracked entity type" />
<SidenavLink label="Program rule" />
<SidenavLink label="Program rule variable" />
</SidenavParent>
<SidenavParent label="Validation">
<SidenavLink label="Validation rule" />
<SidenavLink label="Validation rule group" />
<SidenavLink label="Validation notification" />
</SidenavParent>
<SidenavLink label="Metadata editor" />
</SidenavItems>
</Sidenav>
</aside>
);
};

export default Sidebar;
Empty file.
Loading