@openmrs/esm-user-onboarding-app 4.0.0

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 (51) 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 +34 -0
  11. package/__mocks__/react-i18next.js +50 -0
  12. package/e2e/README.md +115 -0
  13. package/e2e/core/global-setup.ts +32 -0
  14. package/e2e/core/index.ts +1 -0
  15. package/e2e/core/test.ts +20 -0
  16. package/e2e/fixtures/api.ts +26 -0
  17. package/e2e/fixtures/index.ts +1 -0
  18. package/e2e/pages/home-page.ts +9 -0
  19. package/e2e/pages/index.ts +1 -0
  20. package/e2e/specs/onboarding-test.spec.ts +93 -0
  21. package/e2e/support/github/Dockerfile +34 -0
  22. package/e2e/support/github/docker-compose.yml +24 -0
  23. package/e2e/support/github/run-e2e-docker-env.sh +44 -0
  24. package/example.env +6 -0
  25. package/i18next-parser.config.js +89 -0
  26. package/jest.config.js +33 -0
  27. package/package.json +106 -0
  28. package/playwright.config.ts +39 -0
  29. package/prettier.config.js +8 -0
  30. package/src/config-schema.ts +630 -0
  31. package/src/declarations.d.ts +4 -0
  32. package/src/index.ts +22 -0
  33. package/src/root.component.tsx +109 -0
  34. package/src/root.scss +0 -0
  35. package/src/routes.json +28 -0
  36. package/src/setup-tests.ts +1 -0
  37. package/src/tooltip/tooltip.component.tsx +73 -0
  38. package/src/tooltip/tooltip.scss +83 -0
  39. package/src/tutorial/modal.component.test.tsx +98 -0
  40. package/src/tutorial/modal.component.tsx +66 -0
  41. package/src/tutorial/styles.scss +64 -0
  42. package/src/tutorial/tutorial.tsx +22 -0
  43. package/src/types.ts +15 -0
  44. package/translations/am.json +1 -0
  45. package/translations/en.json +9 -0
  46. package/translations/es.json +1 -0
  47. package/translations/fr.json +1 -0
  48. package/translations/he.json +1 -0
  49. package/translations/km.json +1 -0
  50. package/tsconfig.json +23 -0
  51. package/webpack.config.js +1 -0
@@ -0,0 +1 @@
1
+ export * from './api';
@@ -0,0 +1,9 @@
1
+ import { Page } from '@playwright/test';
2
+
3
+ export class HomePage {
4
+ constructor(readonly page: Page) {}
5
+
6
+ async goto() {
7
+ await this.page.goto(`home`);
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ export * from './home-page';
@@ -0,0 +1,93 @@
1
+ import { test } from '@playwright/test';
2
+ import { HomePage } from '../pages';
3
+ import { expect } from '@playwright/test';
4
+
5
+ test('Basic Walkthrough', async ({ page }) => {
6
+ const homePage = new HomePage(page);
7
+
8
+ await test.step('When I visit the home page', async () => {
9
+ await homePage.goto();
10
+ });
11
+
12
+ await test.step('And I click on the help menu button', async () => {
13
+ await page.locator('[id="single-spa-application\\:\\@openmrs\\/esm-help-menu-app-page-0"]').getByRole('button').click();
14
+ });
15
+
16
+ await test.step('And I click on the tutorials button', async () => {
17
+ await page.getByText(/tutorials/i).click();
18
+ });
19
+
20
+ await test.step('Then I should see the tutorial modal', async () => {
21
+ await expect(page.getByRole('heading', { name: 'Tutorials' })).toBeVisible();
22
+ await expect(
23
+ page.getByText(/find walkthroughs and video tutorials on some of the core features of openMRS./i),
24
+ ).toBeVisible();
25
+ });
26
+
27
+ await test.step('And I click on the Basic Overview Tutorial', async () => {
28
+ await page
29
+ .locator('li')
30
+ .filter({ hasText: 'Basic Overview' })
31
+ .locator('a', { hasText: 'Walkthrough' })
32
+ .click();
33
+ });
34
+
35
+ await test.step('Then I should see the first Joyride tooltip', async () => {
36
+ await expect(
37
+ page.getByText(
38
+ 'Welcome to OpenMRS! This is the main dashboard where you can navigate to various features of the system.',
39
+ ),
40
+ ).toBeVisible();
41
+ });
42
+
43
+ await test.step('And I click the next button', async () => {
44
+ await page.getByLabel('Next', { exact: true }).click();
45
+ });
46
+
47
+ await test.step('Then I should see the search icon Joyride tooltip', async () => {
48
+ await expect(
49
+ page.getByText('This is the search icon. Use it to find patients in the system quickly.'),
50
+ ).toBeVisible();
51
+ });
52
+ await test.step('And I click the next button', async () => {
53
+ await page.getByLabel('Next', { exact: true }).click();
54
+ });
55
+
56
+ await test.step('Then I should see the add patient icon Joyride tooltip', async () => {
57
+ await expect(
58
+ page.getByText('This is the add patient icon. Click here to register a new patient into the system.'),
59
+ ).toBeVisible();
60
+ });
61
+ await test.step('And I click the next button', async () => {
62
+ await page.getByLabel('Next', { exact: true }).click();
63
+ });
64
+
65
+ await test.step('Then I should see the user icon Joyride tooltip', async () => {
66
+ await expect(
67
+ page.getByText('The user icon. Click here to change your user preferences and settings.'),
68
+ ).toBeVisible();
69
+ });
70
+ await test.step('And I click the next button', async () => {
71
+ await page.getByLabel('Next', { exact: true }).click();
72
+ });
73
+
74
+ await test.step('Then I should see the active visits Joyride tooltip', async () => {
75
+ await expect(
76
+ page.getByText('This table displays active visits. Here you can see all the ongoing patient visits.'),
77
+ ).toBeVisible();
78
+ });
79
+
80
+ await test.step('And I click the next button', async () => {
81
+ await page.getByLabel('Next', { exact: true }).click();
82
+ });
83
+
84
+ await test.step('And I should see the appointments table Joyride tooltip', async () => {
85
+ await expect(
86
+ page.getByText('This table shows appointments. View and manage patient appointments from this section.'),
87
+ ).toBeVisible();
88
+ });
89
+
90
+ await test.step('And I click the finish button', async () => {
91
+ await page.getByLabel('Last').click();
92
+ });
93
+ });
@@ -0,0 +1,34 @@
1
+ # syntax=docker/dockerfile:1.3
2
+ FROM --platform=$BUILDPLATFORM node:18-alpine as dev
3
+
4
+ ARG APP_SHELL_VERSION=next
5
+
6
+ RUN mkdir -p /app
7
+ WORKDIR /app
8
+
9
+ COPY . .
10
+
11
+ RUN npm_config_legacy_peer_deps=true npm install -g openmrs@${APP_SHELL_VERSION:-next}
12
+ ARG CACHE_BUST
13
+ RUN npm_config_legacy_peer_deps=true openmrs assemble --manifest --mode config --config spa-assemble-config.json --target ./spa
14
+
15
+ FROM --platform=$BUILDPLATFORM openmrs/openmrs-reference-application-3-frontend:nightly as frontend
16
+ FROM nginx:1.23-alpine
17
+
18
+ RUN apk update && \
19
+ apk upgrade && \
20
+ # add more utils for sponge to support our startup script
21
+ apk add --no-cache moreutils
22
+
23
+ # clear any default files installed by nginx
24
+ RUN rm -rf /usr/share/nginx/html/*
25
+
26
+ COPY --from=frontend /etc/nginx/nginx.conf /etc/nginx/nginx.conf
27
+ # this assumes that NOTHING in the framework is in a subdirectory
28
+ COPY --from=frontend /usr/share/nginx/html/* /usr/share/nginx/html/
29
+ COPY --from=frontend /usr/local/bin/startup.sh /usr/local/bin/startup.sh
30
+ RUN chmod +x /usr/local/bin/startup.sh
31
+
32
+ COPY --from=dev /app/spa/ /usr/share/nginx/html/
33
+
34
+ CMD ["/usr/local/bin/startup.sh"]
@@ -0,0 +1,24 @@
1
+ # This docker compose file is used to create a backend environment for the e2e.yml workflow.
2
+ version: '3.7'
3
+
4
+ services:
5
+ gateway:
6
+ image: openmrs/openmrs-reference-application-3-gateway:${TAG:-nightly}
7
+ ports:
8
+ - '8080:80'
9
+
10
+ frontend:
11
+ build:
12
+ context: .
13
+ environment:
14
+ SPA_PATH: /openmrs/spa
15
+ API_URL: /openmrs
16
+
17
+ backend:
18
+ image: openmrs/openmrs-reference-application-3-backend:nightly-with-data
19
+ depends_on:
20
+ - db
21
+
22
+ # MariaDB
23
+ db:
24
+ image: openmrs/openmrs-reference-application-3-db:nightly-with-data
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash -eu
2
+ # get the dir containing the script
3
+ script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
4
+ # create a temporary working directory
5
+ working_dir=$(mktemp -d "${TMPDIR:-/tmp/}openmrs-e2e-frontends.XXXXXXXXXX")
6
+ # get the app name
7
+ app_name=$(jq -r '.name' "$script_dir/../../../package.json")
8
+
9
+ echo "Creating packed archive of the app..."
10
+ # @openmrs/esm-whatever -> _openmrs_esm_whatever
11
+ packed_app_name=$(echo "$app_name" | tr '[:punct:]' '_');
12
+ # run yarn pack for our app and add it to the working directory
13
+ yarn pack -o "$working_dir/$packed_app_name.tgz" >/dev/null;
14
+ echo "Created packed app archives"
15
+
16
+ echo "Creating dynamic spa-assemble-config.json..."
17
+ # dynamically assemble our list of frontend modules, prepending the login app and
18
+ # primary navigation apps; apps will all be in the /app directory of the Docker
19
+ # container
20
+ jq -n \
21
+ --arg app_name "$app_name" \
22
+ --arg app_file "/app/$packed_app_name.tgz" \
23
+ '{
24
+ "@openmrs/esm-primary-navigation-app": "next",
25
+ "@openmrs/esm-home-app": "next",
26
+ "@openmrs/esm-help-menu-app": "next",
27
+ "@openmrs/esm-patient-search-app": "next",
28
+ "@openmrs/esm-patient-registration-app": "next",
29
+ "@openmrs/esm-active-visits-app": "next",
30
+ "@openmrs/esm-appointments-app": "next"
31
+ } + {
32
+ ($app_name): $app_file
33
+ }' | jq '{"frontendModules": .}' > "$working_dir/spa-assemble-config.json"
34
+ echo "Created dynamic spa-assemble-config.json"
35
+
36
+ echo "Copying Docker configuration..."
37
+ cp "$script_dir/Dockerfile" "$working_dir/Dockerfile"
38
+ cp "$script_dir/docker-compose.yml" "$working_dir/docker-compose.yml"
39
+
40
+ cd $working_dir
41
+ echo "Starting Docker containers..."
42
+ # CACHE_BUST to ensure the assemble step is always run
43
+ docker compose build --build-arg CACHE_BUST=$(date +%s) frontend
44
+ docker compose up -d
package/example.env ADDED
@@ -0,0 +1,6 @@
1
+ # This is an example environment file for configuring dynamic values.
2
+ E2E_BASE_URL=http://localhost:8080/openmrs
3
+ E2E_USER_ADMIN_USERNAME=admin
4
+ E2E_USER_ADMIN_PASSWORD=Admin123
5
+ E2E_LOGIN_DEFAULT_LOCATION_UUID=44c3efb0-2583-4c80-a79e-1f756a03c0a1
6
+ # The above location UUID is for the "Outpatient Clinic" location in the reference application
@@ -0,0 +1,89 @@
1
+ module.exports = {
2
+ contextSeparator: '_',
3
+ // Key separator used in your translation keys
4
+
5
+ createOldCatalogs: false,
6
+ // Save the \_old files
7
+
8
+ defaultNamespace: 'translations',
9
+ // Default namespace used in your i18next config
10
+
11
+ defaultValue: '',
12
+ // Default value to give to empty keys
13
+ // You may also specify a function accepting the locale, namespace, and key as arguments
14
+
15
+ indentation: 2,
16
+ // Indentation of the catalog files
17
+
18
+ keepRemoved: false,
19
+ // Keep keys from the catalog that are no longer in code
20
+
21
+ keySeparator: '.',
22
+ // Key separator used in your translation keys
23
+ // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
24
+
25
+ // see below for more details
26
+ lexers: {
27
+ hbs: ['HandlebarsLexer'],
28
+ handlebars: ['HandlebarsLexer'],
29
+
30
+ htm: ['HTMLLexer'],
31
+ html: ['HTMLLexer'],
32
+
33
+ mjs: ['JavascriptLexer'],
34
+ js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
35
+ ts: ['JavascriptLexer'],
36
+ jsx: ['JsxLexer'],
37
+ tsx: ['JsxLexer'],
38
+
39
+ default: ['JavascriptLexer'],
40
+ },
41
+
42
+ lineEnding: 'auto',
43
+ // Control the line ending. See options at https://github.com/ryanve/eol
44
+
45
+ locales: ['en'],
46
+ // An array of the locales in your applications
47
+
48
+ namespaceSeparator: ':',
49
+ // Namespace separator used in your translation keys
50
+ // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
51
+
52
+ output: '$NAMESPACE/$LOCALE.json',
53
+ // Supports $LOCALE and $NAMESPACE injection
54
+ // Supports JSON (.json) and YAML (.yml) file formats
55
+ // Where to write the locale files relative to process.cwd()
56
+
57
+ pluralSeparator: '_',
58
+ // Plural separator used in your translation keys
59
+ // If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
60
+
61
+ input: undefined,
62
+ // An array of globs that describe where to look for source files
63
+ // relative to the location of the configuration file
64
+
65
+ sort: true,
66
+ // Whether or not to sort the catalog
67
+
68
+ useKeysAsDefaultValue: false,
69
+ // Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World"
70
+ // This option takes precedence over the `defaultValue` and `skipDefaultValues` options
71
+ // You may also specify a function accepting the locale and namespace as arguments
72
+
73
+ verbose: false,
74
+ // Display info about the parsing including some stats
75
+
76
+ failOnWarnings: false,
77
+ // Exit with an exit code of 1 on warnings
78
+
79
+ customValueTemplate: null,
80
+ // If you wish to customize the value output the value as an object, you can set your own format.
81
+ // ${defaultValue} is the default value you set in your translation function.
82
+ // Any other custom property will be automatically extracted.
83
+ //
84
+ // Example:
85
+ // {
86
+ // message: "${defaultValue}",
87
+ // description: "${maxLength}", // t('my-key', {maxLength: 150})
88
+ // }
89
+ };
package/jest.config.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @returns {Promise<import('jest').Config>}
3
+ */
4
+ const path = require('path');
5
+
6
+ module.exports = {
7
+ collectCoverageFrom: [
8
+ '**/src/**/*.component.tsx',
9
+ '!**/node_modules/**',
10
+ '!**/vendor/**',
11
+ '!**/src/**/*.test.*',
12
+ '!**/src/declarations.d.ts',
13
+ '!**/e2e/**',
14
+ ],
15
+ transform: {
16
+ '^.+\\.tsx?$': ['@swc/jest'],
17
+ },
18
+ transformIgnorePatterns: ['/node_modules/(?!@openmrs)'],
19
+ moduleNameMapper: {
20
+ '@openmrs/esm-framework': '@openmrs/esm-framework/mock',
21
+ '@openmrs/esm-utils': '@openmrs/esm-framework/mock',
22
+ '\\.(s?css)$': 'identity-obj-proxy',
23
+ '^lodash-es/(.*)$': 'lodash/$1',
24
+ 'lodash-es': 'lodash',
25
+ '^dexie$': require.resolve('dexie'),
26
+ },
27
+ setupFilesAfterEnv: ['<rootDir>/src/setup-tests.ts'],
28
+ testPathIgnorePatterns: [path.resolve(__dirname, 'e2e')],
29
+ testEnvironment: 'jsdom',
30
+ testEnvironmentOptions: {
31
+ url: 'http://localhost/',
32
+ },
33
+ };
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@openmrs/esm-user-onboarding-app",
3
+ "version": "4.0.0",
4
+ "license": "MPL-2.0",
5
+ "description": "An OpenMRS microfrontend for user onboarding walkthroughs",
6
+ "browser": "dist/openmrs-esm-user-onboarding-app.js",
7
+ "main": "src/index.ts",
8
+ "source": true,
9
+ "scripts": {
10
+ "start": "openmrs develop",
11
+ "serve": "webpack serve --mode=development",
12
+ "build": "webpack --mode production",
13
+ "analyze": "webpack --mode=production --env analyze=true",
14
+ "lint": "eslint src --ext js,jsx,ts,tsx --max-warnings 0",
15
+ "prettier": "prettier --write \"src/**/*.{ts,tsx}\" --list-different",
16
+ "typescript": "tsc",
17
+ "test": "jest --config jest.config.js --passWithNoTests",
18
+ "verify": "turbo lint typescript coverage",
19
+ "coverage": "yarn test --coverage",
20
+ "prepare": "husky install",
21
+ "extract-translations": "i18next 'src/**/*.component.tsx' --config ./i18next-parser.config.js",
22
+ "test-e2e": "playwright test"
23
+ },
24
+ "browserslist": [
25
+ "extends browserslist-config-openmrs"
26
+ ],
27
+ "keywords": [
28
+ "openmrs",
29
+ "microfrontends"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/openmrs/openmrs-esm-user-onboarding.git"
34
+ },
35
+ "homepage": "https://github.com/openmrs/openmrs-esm-user-onboarding#readme",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/openmrs/openmrs-esm-user-onboarding/issues"
41
+ },
42
+ "dependencies": {
43
+ "@carbon/react": "^1.33.1",
44
+ "lodash-es": "^4.17.21",
45
+ "react-joyride": "^2.8.2"
46
+ },
47
+ "peerDependencies": {
48
+ "@openmrs/esm-framework": "*",
49
+ "dayjs": "1.x",
50
+ "react": "18.x",
51
+ "react-i18next": "11.x",
52
+ "react-router-dom": "6.x",
53
+ "rxjs": "6.x"
54
+ },
55
+ "devDependencies": {
56
+ "@openmrs/esm-framework": "next",
57
+ "@openmrs/esm-styleguide": "next",
58
+ "@playwright/test": "1.44.0",
59
+ "@swc/cli": "^0.3.12",
60
+ "@swc/core": "^1.3.68",
61
+ "@swc/jest": "^0.2.36",
62
+ "@testing-library/dom": "^10.1.0",
63
+ "@testing-library/jest-dom": "^6.4.5",
64
+ "@testing-library/react": "^15.0.6",
65
+ "@testing-library/user-event": "^14.5.2",
66
+ "@types/jest": "^29.5.12",
67
+ "@types/react": "^18.2.14",
68
+ "@types/react-dom": "^18.2.6",
69
+ "@types/react-router": "^5.1.20",
70
+ "@types/react-router-dom": "^5.3.3",
71
+ "@types/webpack-env": "^1.18.1",
72
+ "@typescript-eslint/eslint-plugin": "^7.8.0",
73
+ "@typescript-eslint/parser": "^7.8.0",
74
+ "css-loader": "^6.8.1",
75
+ "dotenv": "^16.0.3",
76
+ "eslint": "^8.50.0",
77
+ "eslint-config-prettier": "^8.8.0",
78
+ "eslint-plugin-prettier": "^5.1.3",
79
+ "eslint-plugin-react-hooks": "^4.6.2",
80
+ "husky": "^8.0.3",
81
+ "i18next": "^23.2.8",
82
+ "i18next-parser": "^8.0.0",
83
+ "identity-obj-proxy": "^3.0.0",
84
+ "jest": "^29.7.0",
85
+ "jest-cli": "^29.7.0",
86
+ "jest-environment-jsdom": "^29.7.0",
87
+ "lint-staged": "^15.2.2",
88
+ "openmrs": "next",
89
+ "prettier": "^2.8.8",
90
+ "react": "^18.2.0",
91
+ "react-dom": "^18.2.0",
92
+ "react-i18next": "^11.18.6",
93
+ "react-router-dom": "^6.14.1",
94
+ "rxjs": "^6.6.7",
95
+ "swc-loader": "^0.2.3",
96
+ "turbo": "^1.13.3",
97
+ "typescript": "^4.9.5",
98
+ "webpack": "^5.88.1",
99
+ "webpack-cli": "^5.1.4"
100
+ },
101
+ "lint-staged": {
102
+ "packages/**/src/**/*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
103
+ "*.{css,scss,ts,tsx}": "prettier --write --list-different"
104
+ },
105
+ "packageManager": "yarn@4.2.2"
106
+ }
@@ -0,0 +1,39 @@
1
+ import { devices, PlaywrightTestConfig } from '@playwright/test';
2
+ import * as dotenv from 'dotenv';
3
+ dotenv.config();
4
+
5
+ // See https://playwright.dev/docs/test-configuration.
6
+ const config: PlaywrightTestConfig = {
7
+ testDir: './e2e/specs',
8
+ timeout: 3 * 60 * 1000,
9
+ expect: {
10
+ timeout: 40 * 1000,
11
+ },
12
+ fullyParallel: true,
13
+ forbidOnly: !!process.env.CI,
14
+ retries: 0,
15
+ outputDir: "../test-results/results",
16
+ reporter: process.env.CI
17
+ ? [
18
+ ["junit", { outputFile: "results.xml" }],
19
+ ["html"],
20
+ ]
21
+ : [["html", { outputFolder: "./../test-results/report" }]],
22
+ globalSetup: require.resolve('./e2e/core/global-setup'),
23
+ use: {
24
+ baseURL: `${process.env.E2E_BASE_URL}/spa/`,
25
+ storageState: 'e2e/storageState.json',
26
+ video: 'on',
27
+ trace: 'retain-on-failure',
28
+ },
29
+ projects: [
30
+ {
31
+ name: 'chromium',
32
+ use: {
33
+ ...devices['Desktop Chrome'],
34
+ },
35
+ },
36
+ ],
37
+ };
38
+
39
+ export default config;
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ bracketSpacing: true,
3
+ printWidth: 120,
4
+ semi: true,
5
+ singleQuote: true,
6
+ tabWidth: 2,
7
+ trailingComma: 'all',
8
+ };