@ampath/esm-dha-workflow-app 4.0.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +57 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +14 -0
  7. package/.turbo.json +18 -0
  8. package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
  9. package/LICENSE +401 -0
  10. package/README.md +37 -0
  11. package/__mocks__/react-i18next.js +50 -0
  12. package/dist/184.js +2 -0
  13. package/dist/184.js.LICENSE.txt +14 -0
  14. package/dist/184.js.map +1 -0
  15. package/dist/197.js +1 -0
  16. package/dist/255.js +1 -0
  17. package/dist/255.js.map +1 -0
  18. package/dist/282.js +2 -0
  19. package/dist/282.js.LICENSE.txt +32 -0
  20. package/dist/282.js.map +1 -0
  21. package/dist/300.js +1 -0
  22. package/dist/335.js +1 -0
  23. package/dist/353.js +1 -0
  24. package/dist/353.js.map +1 -0
  25. package/dist/420.js +1 -0
  26. package/dist/420.js.map +1 -0
  27. package/dist/540.js +2 -0
  28. package/dist/540.js.LICENSE.txt +9 -0
  29. package/dist/540.js.map +1 -0
  30. package/dist/55.js +1 -0
  31. package/dist/561.js +2 -0
  32. package/dist/561.js.LICENSE.txt +14 -0
  33. package/dist/561.js.map +1 -0
  34. package/dist/652.js +1 -0
  35. package/dist/70.js +1 -0
  36. package/dist/70.js.map +1 -0
  37. package/dist/731.js +2 -0
  38. package/dist/731.js.LICENSE.txt +39 -0
  39. package/dist/731.js.map +1 -0
  40. package/dist/819.js +1 -0
  41. package/dist/819.js.map +1 -0
  42. package/dist/91.js +1 -0
  43. package/dist/91.js.map +1 -0
  44. package/dist/961.js +2 -0
  45. package/dist/961.js.LICENSE.txt +19 -0
  46. package/dist/961.js.map +1 -0
  47. package/dist/99.js +1 -0
  48. package/dist/main.js +1 -0
  49. package/dist/main.js.map +1 -0
  50. package/dist/openmrs-esm-home-app.js +1 -0
  51. package/dist/openmrs-esm-home-app.js.buildmanifest.json +580 -0
  52. package/dist/openmrs-esm-home-app.js.map +1 -0
  53. package/dist/routes.json +1 -0
  54. package/e2e/README.md +115 -0
  55. package/e2e/core/global-setup.ts +32 -0
  56. package/e2e/core/index.ts +1 -0
  57. package/e2e/core/test.ts +20 -0
  58. package/e2e/fixtures/api.ts +26 -0
  59. package/e2e/fixtures/index.ts +1 -0
  60. package/e2e/pages/index.ts +1 -0
  61. package/e2e/pages/root-page.ts +32 -0
  62. package/e2e/specs/template-app.spec.ts +23 -0
  63. package/e2e/support/github/Dockerfile +34 -0
  64. package/e2e/support/github/docker-compose.yml +24 -0
  65. package/e2e/support/github/run-e2e-docker-env.sh +37 -0
  66. package/example.env +6 -0
  67. package/jest.config.js +33 -0
  68. package/package.json +111 -0
  69. package/playwright.config.ts +32 -0
  70. package/prettier.config.js +8 -0
  71. package/src/boxes/extensions/blue-box.component.tsx +15 -0
  72. package/src/boxes/extensions/box.scss +23 -0
  73. package/src/boxes/extensions/brand-box.component.tsx +15 -0
  74. package/src/boxes/extensions/red-box.component.tsx +15 -0
  75. package/src/boxes/slot/boxes.component.tsx +25 -0
  76. package/src/boxes/slot/boxes.scss +29 -0
  77. package/src/config-schema.ts +43 -0
  78. package/src/declarations.d.ts +5 -0
  79. package/src/greeter/greeter.component.tsx +42 -0
  80. package/src/greeter/greeter.scss +20 -0
  81. package/src/greeter/greeter.test.tsx +28 -0
  82. package/src/index.ts +46 -0
  83. package/src/patient-getter/patient-getter.component.tsx +40 -0
  84. package/src/patient-getter/patient-getter.resource.ts +39 -0
  85. package/src/patient-getter/patient-getter.scss +16 -0
  86. package/src/patient-getter/patient-getter.test.tsx +40 -0
  87. package/src/registry/registry.component.tsx +7 -0
  88. package/src/resources/resources.component.tsx +56 -0
  89. package/src/resources/resources.scss +68 -0
  90. package/src/root.component.tsx +39 -0
  91. package/src/root.scss +15 -0
  92. package/src/root.test.tsx +51 -0
  93. package/src/routes.json +19 -0
  94. package/src/side-nav/side-menu.scss +38 -0
  95. package/src/side-nav-menu/nav-links.tsx +22 -0
  96. package/src/widgets/workflow-registry-link.extension.tsx +12 -0
  97. package/tools/i18next-parser.config.js +89 -0
  98. package/tools/setup-tests.ts +1 -0
  99. package/tools/update-openmrs-deps.mjs +43 -0
  100. package/translations/am.json +24 -0
  101. package/translations/en.json +24 -0
  102. package/translations/es.json +24 -0
  103. package/translations/fr.json +24 -0
  104. package/translations/he.json +24 -0
  105. package/translations/km.json +24 -0
  106. package/tsconfig.json +24 -0
  107. package/webpack.config.js +1 -0
@@ -0,0 +1,29 @@
1
+ /* General features of extension styling are controlled
2
+ * at the slot level. The boxes should know as little as
3
+ * possible about the context where they are used, so
4
+ * they do not set their own sizes.
5
+ *
6
+ * `> * > *` is used to target the outermost DOM node of
7
+ * the extension.
8
+ */
9
+ @use '@carbon/layout';
10
+
11
+ .box > * > * {
12
+ height: layout.$spacing-10;
13
+ width: layout.$spacing-10;
14
+ }
15
+
16
+ .boxes {
17
+ margin: layout.$spacing-07 0;
18
+ display: flex;
19
+ gap: layout.$spacing-05;
20
+ }
21
+
22
+ .container {
23
+ max-width: 50rem;
24
+ margin-top: layout.$spacing-09;
25
+
26
+ > * + * {
27
+ margin-top: layout.$spacing-06;
28
+ }
29
+ }
@@ -0,0 +1,43 @@
1
+ import { Type, validator } from '@openmrs/esm-framework';
2
+
3
+ /**
4
+ * This is the config schema. It expects a configuration object which
5
+ * looks like this:
6
+ *
7
+ * ```json
8
+ * { "casualGreeting": true, "whoToGreet": ["Mom"] }
9
+ * ```
10
+ *
11
+ * In OpenMRS Microfrontends, all config parameters are optional. Thus,
12
+ * all elements must have a reasonable default. A good default is one
13
+ * that works well with the reference application.
14
+ *
15
+ * To understand the schema below, please read the configuration system
16
+ * documentation:
17
+ * https://openmrs.github.io/openmrs-esm-core/#/main/config
18
+ * Note especially the section "How do I make my module configurable?"
19
+ * https://openmrs.github.io/openmrs-esm-core/#/main/config?id=im-developing-an-esm-module-how-do-i-make-it-configurable
20
+ * and the Schema Reference
21
+ * https://openmrs.github.io/openmrs-esm-core/#/main/config?id=schema-reference
22
+ */
23
+ export const configSchema = {
24
+ casualGreeting: {
25
+ _type: Type.Boolean,
26
+ _default: false,
27
+ _description: 'Whether to use a casual greeting (or a formal one).',
28
+ },
29
+ whoToGreet: {
30
+ _type: Type.Array,
31
+ _default: ['World'],
32
+ _description: 'Who should be greeted. Names will be separated by a comma and space.',
33
+ _elements: {
34
+ _type: Type.String,
35
+ },
36
+ _validators: [validator((v) => v.length > 0, 'At least one person must be greeted.')],
37
+ },
38
+ };
39
+
40
+ export type Config = {
41
+ casualGreeting: boolean;
42
+ whoToGreet: Array<string>;
43
+ };
@@ -0,0 +1,5 @@
1
+ declare module '*.css';
2
+ declare module '*.scss';
3
+ declare module '*.png';
4
+
5
+ declare type SideNavProps = object;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * This component demonstrates usage of the config object. Its structure
3
+ * comes from `../config-schema.ts`. For more information about the
4
+ * configuration system, read the docs: https://o3-docs.vercel.app/docs/configuration-system
5
+ */
6
+ import React from 'react';
7
+ import { Tile } from '@carbon/react';
8
+ import { Trans, useTranslation } from 'react-i18next';
9
+ import { useConfig } from '@openmrs/esm-framework';
10
+ import { type Config } from '../config-schema';
11
+ import styles from './greeter.scss';
12
+
13
+ const Greeter: React.FC = () => {
14
+ const { t } = useTranslation();
15
+ const config: Config = useConfig();
16
+
17
+ return (
18
+ <div className={styles.container}>
19
+ <h5>{t('configSystem', 'Configuration system')}</h5>
20
+ <p>
21
+ <Trans key="configSystemExplainer">
22
+ The greeting shown below is driven by the configuration system. To change the configuration properties, click
23
+ the spanner icon in the navbar to pull up the Implementer Tools panel. Then, type <em>template</em> into the{' '}
24
+ <em>Search configuration</em> input. This should filter the configuration properties to show only those that
25
+ are relevant to this module. You can change the values of these properties and click <em>Save</em> to see the
26
+ changes reflected in the UI
27
+ </Trans>
28
+ .
29
+ </p>
30
+ <div className={styles.greeting}>
31
+ <Tile className={styles.tile}>
32
+ {config.casualGreeting ? <Trans key="casualGreeting">hey</Trans> : <Trans key="formalGreeting">hello</Trans>}{' '}
33
+ {/* t('world') */}
34
+ {config.whoToGreet.join(', ')}!
35
+ </Tile>
36
+ </div>
37
+ <br />
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default Greeter;
@@ -0,0 +1,20 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+
4
+ .container {
5
+ max-width: 50rem;
6
+
7
+ > * + * {
8
+ margin-top: layout.$spacing-05;
9
+ }
10
+ }
11
+
12
+ .greeting {
13
+ text-transform: capitalize;
14
+ }
15
+
16
+ .tile {
17
+ border: 1px solid lightgray;
18
+ max-width: 15rem;
19
+ @include type.type-style('heading-compact-01');
20
+ }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { useConfig } from '@openmrs/esm-framework';
4
+ import { Config } from '../config-schema';
5
+ import Greeter from './greeter.component';
6
+
7
+ const mockUseConfig = jest.mocked(useConfig<Config>);
8
+
9
+ it('displays the expected default text', () => {
10
+ const config: Config = { casualGreeting: false, whoToGreet: ['World'] };
11
+ mockUseConfig.mockReturnValue(config);
12
+
13
+ render(<Greeter />);
14
+
15
+ expect(screen.getByText(/world/i)).toHaveTextContent('hello World!');
16
+ });
17
+
18
+ it('casually greets my friends', () => {
19
+ const config: Config = {
20
+ casualGreeting: true,
21
+ whoToGreet: ['Ariel', 'Barak', 'Callum'],
22
+ };
23
+ mockUseConfig.mockReturnValue(config);
24
+
25
+ render(<Greeter />);
26
+
27
+ expect(screen.getByText(/ariel/i)).toHaveTextContent('hey Ariel, Barak, Callum!');
28
+ });
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * This is the entrypoint file of the application. It communicates the
3
+ * important features of this microfrontend to the app shell. It
4
+ * connects the app shell to the React application(s) that make up this
5
+ * microfrontend.
6
+ */
7
+ import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
8
+ import { configSchema } from './config-schema';
9
+
10
+ const moduleName = '@ampath/esm-dha-workflow-app';
11
+
12
+ const options = {
13
+ featureName: 'Consulation Workflow',
14
+ moduleName,
15
+ };
16
+
17
+ /**
18
+ * This tells the app shell how to obtain translation files: that they
19
+ * are JSON files in the directory `../translations` (which you should
20
+ * see in the directory structure).
21
+ */
22
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
23
+
24
+ /**
25
+ * This function performs any setup that should happen at microfrontend
26
+ * load-time (such as defining the config schema) and then returns an
27
+ * object which describes how the React application(s) should be
28
+ * rendered.
29
+ */
30
+ export function startupApp() {
31
+ defineConfigSchema(moduleName, configSchema);
32
+ }
33
+
34
+ /**
35
+ * This named export tells the app shell that the default export of `root.component.tsx`
36
+ * should be rendered when the route matches `root`. The full route
37
+ * will be `openmrsSpaBase() + 'root'`, which is usually
38
+ * `/openmrs/spa/root`.
39
+ */
40
+ export const root = getAsyncLifecycle(() => import('./root.component'), options);
41
+ export const registry = getAsyncLifecycle(() => import('./registry/registry.component'), options);
42
+
43
+ export const workflowRegistryLink = getAsyncLifecycle(() => import('./widgets/workflow-registry-link.extension'), {
44
+ featureName: 'workflow-registry-link',
45
+ moduleName,
46
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Components that make queries delegate the query-making logic to a
3
+ * `.resource.ts` function. This component leverages the`usePatient`
4
+ * hook to fetch a patient from the backend. When the button is clicked,
5
+ * the `patientName` variable is set to "test", which triggers the hook
6
+ * to make a request to the backend. The hook returns patient data from the
7
+ * request, which is then rendered in the UI. The hook also returns a boolean
8
+ * property called `isLoading` that is set to true while the request is being
9
+ * made. This component renders a loading indicator while `isLoading` is true.
10
+ */
11
+
12
+ import React, { useState } from 'react';
13
+ import { Button, InlineLoading, Tile } from '@carbon/react';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { usePatient } from './patient-getter.resource';
16
+ import styles from './patient-getter.scss';
17
+
18
+ function PatientGetter() {
19
+ const { t } = useTranslation();
20
+ const [patientName, setPatientName] = useState(null);
21
+ const { patient, isLoading } = usePatient(patientName);
22
+
23
+ return (
24
+ <div className={styles.container}>
25
+ <h5>{t('dataFetching', 'Data fetching')}</h5>
26
+ <p>{t('patientGetterExplainer', 'Try clicking the button below to fetch a patient from the backend')}:</p>
27
+ <Button onClick={() => setPatientName('test')}>{t('getPatient', 'Get a patient named')} 'test'</Button>
28
+ {isLoading ? <InlineLoading description={t('loading', 'Loading') + '...'} role="progressbar" /> : null}
29
+ {patient ? (
30
+ <Tile className={styles.tile}>
31
+ {patient
32
+ ? `${patient.name[0].given} ${patient.name[0].family} / ${patient.gender} / ${patient.birthDate}`
33
+ : null}
34
+ </Tile>
35
+ ) : null}
36
+ </div>
37
+ );
38
+ }
39
+
40
+ export default PatientGetter;
@@ -0,0 +1,39 @@
1
+ import useSWR from 'swr';
2
+ import { fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework';
3
+
4
+ /**
5
+ * This hook searches for a patient using the provided search term from the
6
+ * OpenMRS FHIR API.It leverages the useSWR hook from the SWR library
7
+ * https://swr.vercel.app/docs/data-fetching to fetch data. SWR provides a
8
+ * number of benefits over the standard React useEffect hook, including:
9
+ *
10
+ * - Fast, lightweight and reusable data fetching
11
+ * - Built-in cache and request deduplication
12
+ * - Real-time updates
13
+ * - Simplified error and loading state handling, and more.
14
+ *
15
+ * We recommend using SWR for data fetching in your OpenMRS frontend modules.
16
+ *
17
+ * See the docs for the underlying fhir.js Client object: https://github.com/FHIR/fhir.js#api
18
+ * See the OpenMRS FHIR Module docs: https://wiki.openmrs.org/display/projects/OpenMRS+FHIR+Module
19
+ * See the OpenMRS REST API docs: https://rest.openmrs.org/#openmrs-rest-api
20
+ *
21
+ * @param query A patient name or ID
22
+ * @returns The first matching patient
23
+ */
24
+
25
+ export function usePatient(query: string) {
26
+ const url = `${fhirBaseUrl}/Patient?name=${query}&_summary=data`;
27
+ const { data, error, isLoading } = useSWR<
28
+ {
29
+ data: { entry: Array<{ resource: fhir.Patient }> };
30
+ },
31
+ Error
32
+ >(query ? url : null, openmrsFetch);
33
+
34
+ return {
35
+ patient: data ? data?.data?.entry[0].resource : null,
36
+ error: error,
37
+ isLoading,
38
+ };
39
+ }
@@ -0,0 +1,16 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+
4
+ .container {
5
+ margin-top: layout.$spacing-09;
6
+
7
+ > * + * {
8
+ margin-top: layout.$spacing-06;
9
+ }
10
+ }
11
+
12
+ .tile {
13
+ border: 1px solid lightgray;
14
+ max-width: 20rem;
15
+ @include type.type-style('heading-compact-01');
16
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import PatientGetter from './patient-getter.component';
5
+
6
+ /**
7
+ * This is an idiomatic mock of a backend resource. We generally mock resource fetching functions like `usePatient`, rather than mocking `fetch` or anything lower-level.
8
+ */
9
+ jest.mock('./patient-getter.resource.ts', () => ({
10
+ usePatient: jest.fn(() => ({
11
+ patient: {
12
+ birthDate: '1997-05-21',
13
+ gender: 'male',
14
+ name: [
15
+ {
16
+ family: 'Testguy',
17
+ given: 'Joeboy',
18
+ id: 'abc123',
19
+ },
20
+ ],
21
+ },
22
+ })),
23
+ }));
24
+
25
+ it('gets a patient when the button is clicked', async () => {
26
+ render(<PatientGetter />);
27
+
28
+ const user = userEvent.setup();
29
+ const heading = screen.getByRole('heading', { name: /data fetching/i });
30
+ const button = screen.getByRole('button', {
31
+ name: /get a patient named 'test'/i,
32
+ });
33
+
34
+ expect(heading).toBeInTheDocument();
35
+ expect(button).toBeInTheDocument();
36
+
37
+ await waitFor(() => user.click(button));
38
+
39
+ expect(screen.getByText(/Joeboy Testguy \/ male \/ 1997-05-21/)).toBeInTheDocument();
40
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface RegistryComponentProps {}
3
+ const RegistryComponent: React.FC<RegistryComponentProps> = () => {
4
+ return <>Registry Component</>;
5
+ };
6
+
7
+ export default RegistryComponent;
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { ClickableTile } from '@carbon/react';
3
+ import { ChevronRight } from '@carbon/react/icons';
4
+ import { useTranslation } from 'react-i18next';
5
+ import styles from './resources.scss';
6
+
7
+ function Resources() {
8
+ const { t } = useTranslation();
9
+
10
+ return (
11
+ <div className={styles.resources}>
12
+ <h4 className={styles.heading}>{t('resources', 'Resources')}</h4>
13
+ <span className={styles.explainer}>{t('usefulLinks', 'Below are some links to useful resources')}:</span>
14
+ <div className={styles.cardsContainer}>
15
+ <Card
16
+ title={t('getStarted', 'Get started')}
17
+ subtitle={t('getStartedExplainer', 'Create a frontend module from this template') + '.'}
18
+ link="https://github.com/openmrs/openmrs-esm-home-app/generate"
19
+ />
20
+ <Card
21
+ title={t('frontendDocs', 'Frontend docs')}
22
+ subtitle={t('learnExplainer', 'Learn how to use the O3 framework') + '.'}
23
+ link="https://openmrs.atlassian.net/wiki/spaces/docs/pages/151093495/Introduction+to+O3+for+Developers"
24
+ />
25
+ <Card
26
+ title={t('designDocs', 'Design docs')}
27
+ subtitle={t('designDocsExplainer', 'Read the O3 design documentation') + '.'}
28
+ link="https://zeroheight.com/23a080e38/p/880723-introduction"
29
+ />
30
+ <Card
31
+ title={t('connect', 'Connect')}
32
+ subtitle={t('connectExplainer', 'Get in touch with the community') + '.'}
33
+ link="https://slack.openmrs.org/"
34
+ />
35
+ </div>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ function Card({ title, subtitle, link }: { title: string; subtitle: string; link: string }) {
41
+ return (
42
+ <a href={link} target="_blank" rel="noopener noreferrer" className={styles.cardLink}>
43
+ <ClickableTile className={styles.card}>
44
+ <div className={styles.cardContent}>
45
+ <div className={styles.title}>
46
+ <h4>{title}</h4>
47
+ <ChevronRight />
48
+ </div>
49
+ <span className={styles.subtitle}>{subtitle}</span>
50
+ </div>
51
+ </ClickableTile>
52
+ </a>
53
+ );
54
+ }
55
+
56
+ export default Resources;
@@ -0,0 +1,68 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+
4
+ .container {
5
+ padding: layout.$spacing-07;
6
+ }
7
+
8
+ .heading {
9
+ @include type.type-style('heading-04');
10
+ margin: layout.$spacing-05 0;
11
+ }
12
+
13
+ .explainer {
14
+ display: block;
15
+ }
16
+
17
+ .resources {
18
+ margin-top: layout.$spacing-10;
19
+ margin-bottom: layout.$spacing-09;
20
+
21
+ > * + * {
22
+ margin-right: layout.$spacing-05;
23
+ }
24
+ }
25
+
26
+ .cardsContainer {
27
+ display: grid;
28
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
29
+ column-gap: layout.$spacing-06;
30
+ margin-top: layout.$spacing-07;
31
+ max-width: 75%;
32
+ }
33
+
34
+ .card {
35
+ margin: layout.$spacing-03 0;
36
+ display: flex;
37
+ align-items: center;
38
+
39
+ &:hover {
40
+ border: 1px solid lightgray;
41
+ }
42
+
43
+ svg {
44
+ margin-left: layout.$spacing-02;
45
+ }
46
+ }
47
+
48
+ .cardLink {
49
+ text-decoration: none;
50
+ color: inherit;
51
+ display: block;
52
+ }
53
+
54
+ .cardContent {
55
+ display: flex;
56
+ flex-direction: column;
57
+ }
58
+
59
+ .title {
60
+ display: flex;
61
+ align-items: center;
62
+ margin-bottom: layout.$spacing-05;
63
+
64
+ h4 {
65
+ @include type.type-style('heading-02');
66
+ }
67
+ }
68
+
@@ -0,0 +1,39 @@
1
+ /**
2
+ * From here, the application is pretty typical React, but with lots of
3
+ * support from `@openmrs/esm-framework`. Check out `Greeter` to see
4
+ * usage of the configuration system, and check out `PatientGetter` to
5
+ * see data fetching using the OpenMRS FHIR API.
6
+ *
7
+ * Check out the Config docs:
8
+ * https://openmrs.github.io/openmrs-esm-core/#/main/config
9
+ */
10
+
11
+ import React from 'react';
12
+ import { useTranslation } from 'react-i18next';
13
+ import { Boxes } from './boxes/slot/boxes.component';
14
+ import Greeter from './greeter/greeter.component';
15
+ import PatientGetter from './patient-getter/patient-getter.component';
16
+ import Resources from './resources/resources.component';
17
+ import styles from './root.scss';
18
+
19
+ const Root: React.FC = () => {
20
+ const { t } = useTranslation();
21
+
22
+ return (
23
+ <div className={styles.container}>
24
+ <h3 className={styles.welcome}>{t('welcomeText', 'Welcome to the O3 Template app')}</h3>
25
+ <p className={styles.explainer}>
26
+ {t('explainer', 'The following examples demonstrate some key features of the O3 framework')}.
27
+ </p>
28
+ {/* Greeter: demonstrates the configuration system */}
29
+ <Greeter />
30
+ {/* Boxes: demonstrates the extension system */}
31
+ <Boxes />
32
+ {/* PatientGetter: demonstrates data fetching */}
33
+ <PatientGetter />
34
+ <Resources />
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export default Root;
package/src/root.scss ADDED
@@ -0,0 +1,15 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+
4
+ .container {
5
+ padding: layout.$spacing-07;
6
+ }
7
+
8
+ .welcome {
9
+ @include type.type-style('heading-04');
10
+ margin: layout.$spacing-05 0;
11
+ }
12
+
13
+ .explainer {
14
+ margin-bottom: layout.$spacing-07;
15
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * This is the root test for this page. It simply checks that the page
3
+ * renders. If the components of your page are highly interdependent,
4
+ * (e.g., if the `Root` component had state that communicated
5
+ * information between `Greeter` and `PatientGetter`) then you might
6
+ * want to do most of your testing here. If those components are
7
+ * instead quite independent (as is the case in this example), then
8
+ * it would make more sense to test those components independently.
9
+ *
10
+ * The key thing to remember, always, is: write tests that behave like
11
+ * users. They should *look* for elements by their visual
12
+ * characteristics, *interact* with them, and (mostly) *assert* based
13
+ * on things that would be visually apparent to a user.
14
+ *
15
+ * To learn more about how we do testing, see the following resources:
16
+ * https://o3-docs.vercel.app/docs/frontend-modules/testing
17
+ * https://kentcdodds.com/blog/how-to-know-what-to-test
18
+ * https://kentcdodds.com/blog/testing-implementation-details
19
+ * https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
20
+ *
21
+ * Kent C. Dodds is the inventor of `@testing-library`:
22
+ * https://testing-library.com/docs/guiding-principles
23
+ */
24
+ import React from 'react';
25
+ import { render, screen } from '@testing-library/react';
26
+ import { useConfig } from '@openmrs/esm-framework';
27
+ import { Config } from './config-schema';
28
+ import Root from './root.component';
29
+
30
+ /**
31
+ * This is an idiomatic way of dealing with mocked files. Note that
32
+ * `useConfig` is already mocked; the Jest moduleNameMapper (see the
33
+ * Jest config) has mapped the `@openmrs/esm-framework` import to a
34
+ * mock file. This line just tells TypeScript that the object is, in
35
+ * fact, a mock, and so will have methods like `mockReturnValue`.
36
+ */
37
+ const mockUseConfig = jest.mocked(useConfig<Config>);
38
+
39
+ it('renders a landing page for the Home app', () => {
40
+ const config: Config = { casualGreeting: false, whoToGreet: ['World'] };
41
+ mockUseConfig.mockReturnValue(config);
42
+
43
+ render(<Root />);
44
+
45
+ expect(screen.getByRole('heading', { name: /welcome to the o3 home app/i })).toBeInTheDocument();
46
+ expect(screen.getByRole('heading', { name: /configuration system/i })).toBeInTheDocument();
47
+ expect(screen.getByRole('heading', { name: /extension system/i })).toBeInTheDocument();
48
+ expect(screen.getByRole('heading', { name: /data fetching/i })).toBeInTheDocument();
49
+ expect(screen.getByRole('heading', { name: /resources/i })).toBeInTheDocument();
50
+ expect(screen.getByRole('button', { name: /get a patient named 'test'/i })).toBeInTheDocument();
51
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {},
4
+ "extensions": [
5
+ {
6
+ "component": "workflowRegistryLink",
7
+ "name": "workflow-registry-link",
8
+ "slot": "app-menu-slot",
9
+ "online": true,
10
+ "offline": true
11
+ }
12
+ ],
13
+ "pages": [
14
+ {
15
+ "component": "registry",
16
+ "route": "workflow"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,38 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .link > div a:nth-child(1) {
6
+ height: layout.$spacing-09;
7
+ padding: layout.$spacing-06 0 layout.$spacing-06 1.25rem;
8
+ color: $ui-04;
9
+ @extend .productiveHeading01;
10
+ border-left: layout.$spacing-02 solid transparent;
11
+ }
12
+
13
+ :global(.omrs-breakpoint-gt-tablet) .link > div {
14
+ padding-top: layout.$spacing-05;
15
+ }
16
+
17
+ :global(.omrs-breakpoint-gt-tablet) .link > div a:nth-child(1) {
18
+ height: layout.$spacing-07;
19
+ color: $ui-04;
20
+ padding-left: layout.$spacing-05;
21
+ }
22
+
23
+ .link > div a:nth-child(1):hover {
24
+ background-color: $ui-03;
25
+ color: $ui-05;
26
+ border-left: var(--brand-01);
27
+ }
28
+
29
+ .link > div a:nth-child(1):focus {
30
+ background-color: $ui-03;
31
+ outline: none;
32
+ color: $ui-05;
33
+ border-left: var(--brand-01);
34
+ }
35
+
36
+ .productiveHeading01 {
37
+ @include type.type-style('heading-compact-01');
38
+ }