@openmrs/esm-login-app 3.2.0 → 3.2.1-pre.1015

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 (47) hide show
  1. package/__mocks__/config.mock.ts +18 -0
  2. package/__mocks__/react-i18next.js +1 -1
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.map +1 -1
  5. package/dist/216.js +1 -1
  6. package/dist/216.js.map +1 -1
  7. package/dist/217.js +1 -1
  8. package/dist/217.js.map +1 -1
  9. package/dist/25.js +1 -1
  10. package/dist/25.js.map +1 -1
  11. package/dist/309.js +1 -1
  12. package/dist/309.js.map +1 -1
  13. package/dist/393.js +1 -1
  14. package/dist/393.js.map +1 -1
  15. package/dist/574.js +1 -1
  16. package/dist/592.js +1 -1
  17. package/dist/592.js.map +1 -1
  18. package/dist/636.js +3 -0
  19. package/dist/{419.js.LICENSE.txt → 636.js.LICENSE.txt} +0 -0
  20. package/dist/{419.js.map → 636.js.map} +1 -1
  21. package/dist/695.js +1 -1
  22. package/dist/695.js.map +1 -1
  23. package/dist/743.js +2 -0
  24. package/dist/743.js.map +1 -0
  25. package/dist/747.js +1 -1
  26. package/dist/747.js.map +1 -1
  27. package/dist/788.js +1 -1
  28. package/dist/788.js.map +1 -1
  29. package/dist/openmrs-esm-login-app.js +1 -2
  30. package/dist/openmrs-esm-login-app.js.buildmanifest.json +89 -89
  31. package/dist/openmrs-esm-login-app.js.map +1 -1
  32. package/dist/openmrs-esm-login-app.old +2 -0
  33. package/jest.config.js +2 -4
  34. package/package.json +4 -3
  35. package/src/change-location-link/change-location-link.component.test.tsx +4 -2
  36. package/src/choose-location/choose-location.component.test.tsx +91 -71
  37. package/src/location-picker/location-picker.component.test.tsx +44 -43
  38. package/src/login/login.component.test.tsx +53 -37
  39. package/src/login/login.component.tsx +5 -0
  40. package/translations/en.json +1 -1
  41. package/__mocks__/lodash.debounce.mock.ts +0 -3
  42. package/__mocks__/lodash.isEmpty.mock.ts +0 -3
  43. package/__mocks__/openmrs-esm-framework.mock.tsx +0 -68
  44. package/dist/419.js +0 -3
  45. package/dist/684.js +0 -2
  46. package/dist/684.js.map +0 -1
  47. package/src/root.component.test.tsx +0 -10
@@ -2,13 +2,9 @@ import "@testing-library/jest-dom";
2
2
  import React from "react";
3
3
  import LocationPicker from "./location-picker.component";
4
4
  import { act } from "react-dom/test-utils";
5
- import {
6
- cleanup,
7
- fireEvent,
8
- render,
9
- wait,
10
- screen,
11
- } from "@testing-library/react";
5
+ import { fireEvent, render, waitFor, screen } from "@testing-library/react";
6
+ import { useConfig } from "@openmrs/esm-framework";
7
+ import { mockConfig } from "../../__mocks__/config.mock";
12
8
 
13
9
  const loginLocations = {
14
10
  data: {
@@ -19,11 +15,14 @@ const loginLocations = {
19
15
  },
20
16
  };
21
17
 
18
+ const mockedUseConfig = useConfig as jest.Mock;
19
+
20
+ jest.mock("lodash-es/debounce", () => jest.fn((fn) => fn));
21
+
22
22
  describe(`<LocationPicker />`, () => {
23
23
  let searchInput,
24
24
  marsInput,
25
25
  submitButton,
26
- wrapper,
27
26
  locationEntries,
28
27
  onChangeLocation,
29
28
  searchLocations;
@@ -37,13 +36,15 @@ describe(`<LocationPicker />`, () => {
37
36
  writable: true,
38
37
  });
39
38
 
39
+ mockedUseConfig.mockReturnValue(mockConfig);
40
+
40
41
  // reset mocks
41
42
  locationEntries = loginLocations.data.entry;
42
43
  onChangeLocation = jest.fn(() => {});
43
44
  searchLocations = jest.fn(() => Promise.resolve([]));
44
45
 
45
- //prepare components
46
- wrapper = render(
46
+ // prepare components
47
+ render(
47
48
  <LocationPicker
48
49
  loginLocations={locationEntries}
49
50
  onChangeLocation={onChangeLocation}
@@ -53,16 +54,14 @@ describe(`<LocationPicker />`, () => {
53
54
  />
54
55
  );
55
56
 
56
- searchInput = wrapper.container.querySelector("input");
57
- submitButton = wrapper.getByText("Confirm", { selector: "button" });
57
+ searchInput = screen.getByRole("searchbox");
58
+ submitButton = screen.getByText("Confirm", { selector: "button" });
58
59
  });
59
60
 
60
- afterEach(cleanup);
61
-
62
61
  it("trigger search on typing", async () => {
63
- cleanup();
64
62
  searchLocations = jest.fn(() => Promise.resolve(loginLocations));
65
- wrapper = render(
63
+
64
+ render(
66
65
  <LocationPicker
67
66
  loginLocations={locationEntries}
68
67
  onChangeLocation={onChangeLocation}
@@ -74,8 +73,8 @@ describe(`<LocationPicker />`, () => {
74
73
 
75
74
  fireEvent.change(searchInput, { target: { value: "mars" } });
76
75
 
77
- await wait(() => {
78
- expect(wrapper.getByLabelText("Mars")).not.toBeNull();
76
+ await waitFor(() => {
77
+ expect(screen.getByLabelText("Mars")).not.toBeNull();
79
78
  });
80
79
  });
81
80
 
@@ -84,16 +83,16 @@ describe(`<LocationPicker />`, () => {
84
83
  fireEvent.change(searchInput, { target: { value: "Mars" } });
85
84
  });
86
85
 
87
- await wait(() => {
88
- expect(wrapper.queryByText("Mars")).not.toBeNull();
89
- marsInput = wrapper.getByLabelText("Mars");
86
+ await waitFor(() => {
87
+ expect(screen.queryByText("Mars")).not.toBeNull();
88
+ marsInput = screen.getByLabelText("Mars");
90
89
  });
91
90
 
92
91
  act(() => {
93
92
  fireEvent.click(marsInput);
94
93
  });
95
94
 
96
- await wait(() => {
95
+ await waitFor(() => {
97
96
  expect(submitButton).not.toHaveAttribute("disabled");
98
97
  });
99
98
  });
@@ -105,15 +104,15 @@ describe(`<LocationPicker />`, () => {
105
104
  fireEvent.change(searchInput, { target: { value: "Mars" } });
106
105
  });
107
106
 
108
- await wait(() => {
109
- expect(wrapper.queryByText("Mars")).not.toBeNull();
110
- marsInput = wrapper.getByLabelText("Mars");
107
+ await waitFor(() => {
108
+ expect(screen.queryByText("Mars")).not.toBeNull();
109
+ marsInput = screen.getByLabelText("Mars");
111
110
  });
112
111
 
113
112
  fireEvent.click(marsInput);
114
113
  fireEvent.click(submitButton);
115
114
 
116
- await wait(() => expect(onChangeLocation).toHaveBeenCalled());
115
+ await waitFor(() => expect(onChangeLocation).toHaveBeenCalled());
117
116
  });
118
117
 
119
118
  it(`send the user to the home page on submit`, async () => {
@@ -123,15 +122,15 @@ describe(`<LocationPicker />`, () => {
123
122
  fireEvent.change(searchInput, { target: { value: "Mars" } });
124
123
  });
125
124
 
126
- await wait(() => {
127
- expect(wrapper.queryByText("Mars")).not.toBeNull();
128
- marsInput = wrapper.getByLabelText("Mars");
125
+ await waitFor(() => {
126
+ expect(screen.queryByText("Mars")).not.toBeNull();
127
+ marsInput = screen.getByLabelText("Mars");
129
128
  });
130
129
 
131
130
  fireEvent.click(marsInput);
132
131
  fireEvent.click(submitButton);
133
132
 
134
- await wait(() => {
133
+ await waitFor(() => {
135
134
  expect(onChangeLocation).toHaveBeenCalled();
136
135
  });
137
136
  });
@@ -143,17 +142,17 @@ describe(`<LocationPicker />`, () => {
143
142
  fireEvent.change(searchInput, { target: { value: "Mars" } });
144
143
  });
145
144
 
146
- await wait(() => {
147
- expect(wrapper.queryByText("Mars")).not.toBeNull();
148
- marsInput = wrapper.getByLabelText("Mars");
145
+ await waitFor(() => {
146
+ expect(screen.queryByText("Mars")).not.toBeNull();
147
+ marsInput = screen.getByLabelText("Mars");
149
148
  });
150
149
 
151
- submitButton = wrapper.getByText("Confirm", { selector: "button" });
150
+ submitButton = screen.getByText("Confirm", { selector: "button" });
152
151
 
153
152
  fireEvent.click(marsInput);
154
153
  fireEvent.click(submitButton);
155
154
 
156
- await wait(() => {
155
+ await waitFor(() => {
157
156
  expect(onChangeLocation).toHaveBeenCalled();
158
157
  });
159
158
  });
@@ -163,7 +162,7 @@ describe(`<LocationPicker />`, () => {
163
162
  });
164
163
 
165
164
  it("should deselect active location when user searches for a location", async () => {
166
- const locationRadioButton: HTMLElement = await wrapper.getByRole("radio", {
165
+ const locationRadioButton: HTMLElement = await screen.getByRole("radio", {
167
166
  name: /Earth/,
168
167
  });
169
168
  fireEvent.click(locationRadioButton);
@@ -175,9 +174,9 @@ describe(`<LocationPicker />`, () => {
175
174
  it("shows error message when no matching locations can be found", async () => {
176
175
  fireEvent.change(searchInput, { target: { value: "doof" } });
177
176
 
178
- await wait(() => {
177
+ await waitFor(() => {
179
178
  expect(
180
- wrapper.getByText("Sorry, no matching location was found")
179
+ screen.getByText("Sorry, no matching location was found")
181
180
  ).not.toBeNull();
182
181
  });
183
182
  expect(submitButton).toHaveAttribute("disabled");
@@ -187,14 +186,14 @@ describe(`<LocationPicker />`, () => {
187
186
  expect(
188
187
  window.localStorage.getItem("userDefaultLoginLocationKeyDemo")
189
188
  ).toEqual("111");
190
- const locationRadioButton: HTMLElement = await wrapper.getByRole("radio", {
189
+ const locationRadioButton: HTMLElement = await screen.getByRole("radio", {
191
190
  name: /Earth/,
192
191
  });
193
192
  expect(locationRadioButton).toHaveProperty("checked", true);
194
193
  });
195
194
 
196
195
  it("should set user Default location when location is changed", async () => {
197
- const locationRadioButton: HTMLElement = await wrapper.findByLabelText(
196
+ const locationRadioButton: HTMLElement = await screen.findByLabelText(
198
197
  /Earth/
199
198
  );
200
199
  fireEvent.click(locationRadioButton);
@@ -207,7 +206,7 @@ describe(`<LocationPicker />`, () => {
207
206
 
208
207
  it("should display the correct pageSize", async () => {
209
208
  expect(screen.getByText(/Showing 2 of 2 locations/i)).toBeInTheDocument();
210
- cleanup();
209
+
211
210
  const loginLocations: any = {
212
211
  data: {
213
212
  entry: [
@@ -217,7 +216,8 @@ describe(`<LocationPicker />`, () => {
217
216
  ],
218
217
  },
219
218
  };
220
- const wrapper = render(
219
+
220
+ render(
221
221
  <LocationPicker
222
222
  loginLocations={loginLocations.data.entry}
223
223
  onChangeLocation={onChangeLocation}
@@ -226,6 +226,7 @@ describe(`<LocationPicker />`, () => {
226
226
  isLoginEnabled={true}
227
227
  />
228
228
  );
229
- expect(wrapper.getByText(/Showing 3 of 3 locations/i)).toBeInTheDocument();
229
+
230
+ expect(screen.getByText(/Showing 3 of 3 locations/i)).toBeInTheDocument();
230
231
  });
231
232
  });
@@ -1,12 +1,13 @@
1
1
  import "@testing-library/jest-dom";
2
2
  import Login from "./login.component";
3
3
  import { useState } from "react";
4
- import { cleanup, wait } from "@testing-library/react";
5
- import { setSessionLocation } from "@openmrs/esm-framework";
4
+ import { waitFor, screen } from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
+ import { setSessionLocation, useConfig } from "@openmrs/esm-framework";
6
7
  import { performLogin } from "./login.resource";
7
8
  import { useCurrentUser } from "../CurrentUserContext";
9
+ import { mockConfig } from "../../__mocks__/config.mock";
8
10
  import renderWithRouter from "../test-helpers/render-with-router";
9
- import userEvent from "@testing-library/user-event";
10
11
 
11
12
  const mockedLogin = performLogin as jest.Mock;
12
13
 
@@ -16,6 +17,7 @@ jest.mock("./login.resource", () => ({
16
17
 
17
18
  const mockedSetSessionLocation = setSessionLocation as jest.Mock;
18
19
  const mockedUseCurrentUser = useCurrentUser as jest.Mock;
20
+ const mockedUseConfig = useConfig as jest.Mock;
19
21
 
20
22
  jest.mock("../CurrentUserContext", () => ({
21
23
  useCurrentUser: jest.fn(),
@@ -31,22 +33,23 @@ describe(`<Login />`, () => {
31
33
  mockedLogin.mockReset();
32
34
  mockedSetSessionLocation.mockReset();
33
35
  mockedUseCurrentUser.mockReset();
36
+ mockedUseConfig.mockReturnValue(mockConfig);
34
37
  });
35
38
 
36
- afterEach(cleanup);
37
-
38
39
  it(`renders a login form`, () => {
39
- const wrapper = renderWithRouter(Login, {
40
+ renderWithRouter(Login, {
40
41
  loginLocations: loginLocations,
41
42
  isLoginEnabled: true,
42
43
  });
43
44
 
44
- wrapper.getByRole("textbox", { name: /Username/i });
45
- wrapper.getByRole("button", { name: /Continue/i });
45
+ screen.getByRole("img", { name: /OpenMRS logo/i });
46
+ expect(screen.queryByAltText(/logo/i)).not.toBeInTheDocument();
47
+ screen.getByRole("textbox", { name: /Username/i });
48
+ screen.getByRole("button", { name: /Continue/i });
46
49
  });
47
50
 
48
51
  it(`should return user focus to username input when input is invalid`, () => {
49
- const wrapper = renderWithRouter(
52
+ renderWithRouter(
50
53
  Login,
51
54
  {
52
55
  loginLocations: loginLocations,
@@ -59,25 +62,22 @@ describe(`<Login />`, () => {
59
62
  );
60
63
 
61
64
  expect(
62
- wrapper.getByRole("textbox", { name: /username/i })
65
+ screen.getByRole("textbox", { name: /username/i })
63
66
  ).toBeInTheDocument();
64
- userEvent.type(wrapper.getByRole("textbox", { name: /Username/i }), "");
65
- const continueButton = wrapper.getByRole("button", { name: /Continue/i });
67
+ userEvent.type(screen.getByRole("textbox", { name: /Username/i }), "");
68
+ const continueButton = screen.getByRole("button", { name: /Continue/i });
66
69
  userEvent.click(continueButton);
67
- expect(wrapper.getByRole("textbox", { name: /username/i })).toHaveFocus();
68
- userEvent.type(
69
- wrapper.getByRole("textbox", { name: /Username/i }),
70
- "yoshi"
71
- );
70
+ expect(screen.getByRole("textbox", { name: /username/i })).toHaveFocus();
71
+ userEvent.type(screen.getByRole("textbox", { name: /Username/i }), "yoshi");
72
72
  userEvent.click(continueButton);
73
- userEvent.type(wrapper.getByLabelText("password"), "yoshi");
74
- expect(wrapper.getByLabelText(/password/i)).toHaveFocus();
73
+ userEvent.type(screen.getByLabelText("password"), "yoshi");
74
+ expect(screen.getByLabelText(/password/i)).toHaveFocus();
75
75
  });
76
76
 
77
77
  it(`makes an API request when you submit the form`, async () => {
78
78
  mockedLogin.mockReturnValue(Promise.resolve({ some: "data" }));
79
79
 
80
- const wrapper = renderWithRouter(
80
+ renderWithRouter(
81
81
  Login,
82
82
  {
83
83
  loginLocations: loginLocations,
@@ -90,18 +90,16 @@ describe(`<Login />`, () => {
90
90
  );
91
91
 
92
92
  expect(performLogin).not.toHaveBeenCalled();
93
- userEvent.type(
94
- wrapper.getByRole("textbox", { name: /Username/i }),
95
- "yoshi"
93
+ userEvent.type(screen.getByRole("textbox", { name: /Username/i }), "yoshi");
94
+ userEvent.click(screen.getByRole("button", { name: /Continue/i }));
95
+ userEvent.type(screen.getByLabelText("password"), "no-tax-fraud");
96
+ userEvent.click(screen.getByRole("button", { name: /submit/i }));
97
+ await waitFor(() =>
98
+ expect(performLogin).toHaveBeenCalledWith("yoshi", "no-tax-fraud")
96
99
  );
97
- userEvent.click(wrapper.getByRole("button", { name: /Continue/i }));
98
- userEvent.type(wrapper.getByLabelText("password"), "no-tax-fraud");
99
- userEvent.click(wrapper.getByRole("button", { name: /submit/i }));
100
- await wait();
101
- expect(performLogin).toHaveBeenCalledWith("yoshi", "no-tax-fraud");
102
100
  });
103
101
 
104
- it(`send the user to the location select page on login if there is more than one location`, async () => {
102
+ it(`sends the user to the location select page on login if there is more than one location`, async () => {
105
103
  let refreshUser = (user: any) => {};
106
104
  mockedLogin.mockImplementation(() => {
107
105
  refreshUser({
@@ -130,15 +128,33 @@ describe(`<Login />`, () => {
130
128
  }
131
129
  );
132
130
 
133
- userEvent.type(
134
- wrapper.getByRole("textbox", { name: /Username/i }),
135
- "yoshi"
131
+ userEvent.type(screen.getByRole("textbox", { name: /Username/i }), "yoshi");
132
+ userEvent.click(screen.getByRole("button", { name: /Continue/i }));
133
+ userEvent.type(screen.getByLabelText("password"), "no-tax-fraud");
134
+ userEvent.click(screen.getByRole("button", { name: /submit/i }));
135
+ await waitFor(() =>
136
+ expect(wrapper.history.location.pathname).toBe("/login/location")
136
137
  );
137
- userEvent.click(wrapper.getByRole("button", { name: /Continue/i }));
138
- userEvent.type(wrapper.getByLabelText("password"), "no-tax-fraud");
139
- userEvent.click(wrapper.getByRole("button", { name: /submit/i }));
140
- await wait();
138
+ });
139
+ it("respects the logo configuration", () => {
140
+ const customLogoConfig = {
141
+ src: "https://some-image-host.com/foo.png",
142
+ alt: "Custom logo",
143
+ };
144
+ mockedUseConfig.mockReturnValue({
145
+ ...mockConfig,
146
+ logo: customLogoConfig,
147
+ });
148
+
149
+ renderWithRouter(Login, {
150
+ loginLocations: loginLocations,
151
+ isLoginEnabled: true,
152
+ });
153
+
154
+ const logo = screen.getByAltText(customLogoConfig.alt);
141
155
 
142
- expect(wrapper.history.location.pathname).toBe("/login/location");
156
+ expect(screen.queryByTitle(/openmrs logo/i)).not.toBeInTheDocument();
157
+ expect(logo).toHaveAttribute("src", customLogoConfig.src);
158
+ expect(logo).toHaveAttribute("alt", customLogoConfig.alt);
143
159
  });
144
160
  });
@@ -123,6 +123,7 @@ const Login: React.FC<LoginProps> = ({ history, location, isLoginEnabled }) => {
123
123
  />
124
124
  ) : (
125
125
  <svg role="img" className={styles["logo"]}>
126
+ <title>OpenMRS logo</title>
126
127
  <use xlinkHref="#omrs-logo-full-color"></use>
127
128
  </svg>
128
129
  );
@@ -134,6 +135,10 @@ const Login: React.FC<LoginProps> = ({ history, location, isLoginEnabled }) => {
134
135
  <InlineNotification
135
136
  kind="error"
136
137
  style={{ width: "23rem" }}
138
+ /**
139
+ * This comment tells i18n to still keep the following translation keys (used as value for: errorMessage):
140
+ * t('invalidCredentials')
141
+ */
137
142
  subtitle={t(errorMessage)}
138
143
  title={t("error", "Error")}
139
144
  onClick={() => setErrorMessage("")}
@@ -4,8 +4,8 @@
4
4
  "contactAdmin": "Contact the site administrator",
5
5
  "continue": "Continue",
6
6
  "error": "Error",
7
- "invalidCredentials": "Invalid username or password",
8
7
  "found": "found",
8
+ "invalidCredentials": "Invalid username or password",
9
9
  "locationNotFound": "Sorry, no matching location was found.",
10
10
  "locations": "locations",
11
11
  "login": "Log in",
@@ -1,3 +0,0 @@
1
- const debounce = jest.fn((fn) => fn);
2
-
3
- export default debounce;
@@ -1,3 +0,0 @@
1
- const isEmpty = jest.fn((arr) => arr.length === 0);
2
-
3
- export default isEmpty;
@@ -1,68 +0,0 @@
1
- import { of } from "rxjs";
2
- import React from "react";
3
-
4
- export function openmrsFetch() {
5
- return new Promise(() => {});
6
- }
7
-
8
- export function getCurrentUser() {
9
- return of({ authenticated: false });
10
- }
11
-
12
- export function defineConfigSchema() {}
13
-
14
- export const setSessionLocation = jest.fn(() => Promise.resolve());
15
-
16
- export const validators = {
17
- isBoolean: jest.fn(),
18
- isString: jest.fn(),
19
- };
20
-
21
- export const navigate = jest.fn();
22
-
23
- export const Type = {
24
- Array: "Array",
25
- Boolean: "Boolean",
26
- ConceptUuid: "ConceptUuid",
27
- Number: "Number",
28
- Object: "Object",
29
- String: "String",
30
- UUID: "UUID",
31
- };
32
-
33
- export function createErrorHandler() {
34
- return function errorHandler(err) {
35
- console.log(`Received error ${err}`);
36
- };
37
- }
38
-
39
- export const openmrsComponentDecorator = jest
40
- .fn()
41
- .mockImplementation(() => (f) => f);
42
-
43
- export const config = {
44
- provider: {
45
- type: "basic",
46
- loginUrl: "",
47
- logoutUrl: "",
48
- },
49
- chooseLocation: {
50
- enabled: true,
51
- numberToShow: 3,
52
- },
53
- logo: {
54
- src: null,
55
- alt: "Logo",
56
- },
57
- links: {
58
- loginSuccess: "${openmrsSpaBase}/home",
59
- },
60
- };
61
-
62
- export function useConfig() {
63
- return config;
64
- }
65
-
66
- export const ComponentContext = React.createContext({
67
- moduleName: "fake-module-config",
68
- });