UserProfileEditPage.test.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { within } from '@testing-library/dom';
  2. import { render, screen, waitFor } from '@testing-library/react';
  3. import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
  4. import React from 'react';
  5. import { OrgRole } from '@grafana/data';
  6. import { selectors } from '@grafana/e2e-selectors';
  7. import TestProvider from '../../../test/helpers/TestProvider';
  8. import { getNavModel } from '../../core/selectors/navModel';
  9. import { backendSrv } from '../../core/services/backend_srv';
  10. import { TeamPermissionLevel } from '../../types';
  11. import { Props, UserProfileEditPage } from './UserProfileEditPage';
  12. import { initialUserState } from './state/reducers';
  13. const defaultProps: Props = {
  14. ...initialUserState,
  15. user: {
  16. id: 1,
  17. name: 'Test User',
  18. email: 'test@test.com',
  19. login: 'test',
  20. isDisabled: false,
  21. isGrafanaAdmin: false,
  22. orgId: 0,
  23. },
  24. teams: [
  25. {
  26. id: 0,
  27. name: 'Team One',
  28. email: 'team.one@test.com',
  29. avatarUrl: '/avatar/07d881f402480a2a511a9a15b5fa82c0',
  30. memberCount: 2000,
  31. permission: TeamPermissionLevel.Admin,
  32. },
  33. ],
  34. orgs: [
  35. {
  36. name: 'Main',
  37. orgId: 0,
  38. role: OrgRole.Editor,
  39. },
  40. {
  41. name: 'Second',
  42. orgId: 1,
  43. role: OrgRole.Viewer,
  44. },
  45. {
  46. name: 'Third',
  47. orgId: 2,
  48. role: OrgRole.Admin,
  49. },
  50. ],
  51. sessions: [
  52. {
  53. id: 0,
  54. browser: 'Chrome',
  55. browserVersion: '90',
  56. clientIp: 'localhost',
  57. createdAt: '2021-01-01 04:00:00',
  58. device: 'Macbook Pro',
  59. isActive: true,
  60. os: 'Mac OS X',
  61. osVersion: '11',
  62. seenAt: new Date().toUTCString(),
  63. },
  64. ],
  65. navModel: getNavModel(
  66. {
  67. 'profile-settings': {
  68. icon: 'sliders-v-alt',
  69. id: 'profile-settings',
  70. parentItem: {
  71. id: 'profile',
  72. text: 'Test User',
  73. img: '/avatar/46d229b033af06a191ff2267bca9ae56',
  74. url: '/profile',
  75. },
  76. text: 'Preferences',
  77. url: '/profile',
  78. },
  79. },
  80. 'profile-settings'
  81. ),
  82. initUserProfilePage: jest.fn().mockResolvedValue(undefined),
  83. revokeUserSession: jest.fn().mockResolvedValue(undefined),
  84. changeUserOrg: jest.fn().mockResolvedValue(undefined),
  85. updateUserProfile: jest.fn().mockResolvedValue(undefined),
  86. };
  87. function getSelectors() {
  88. const dashboardSelect = () => screen.getByTestId('User preferences home dashboard drop down');
  89. const timepickerSelect = () => screen.getByTestId(selectors.components.TimeZonePicker.containerV2);
  90. const teamsTable = () => screen.getByRole('table', { name: /user teams table/i });
  91. const orgsTable = () => screen.getByTestId(selectors.components.UserProfile.orgsTable);
  92. const sessionsTable = () => screen.getByTestId(selectors.components.UserProfile.sessionsTable);
  93. return {
  94. name: () => screen.getByRole('textbox', { name: /^name$/i }),
  95. email: () => screen.getByRole('textbox', { name: /email/i }),
  96. username: () => screen.getByRole('textbox', { name: /username/i }),
  97. saveProfile: () => screen.getByTestId(selectors.components.UserProfile.profileSaveButton),
  98. dashboardSelect,
  99. dashboardValue: () => within(dashboardSelect()).getByText(/default/i),
  100. timepickerSelect,
  101. timepickerValue: () => within(timepickerSelect()).getByText(/coordinated universal time/i),
  102. savePreferences: () => screen.getByTestId(selectors.components.UserProfile.preferencesSaveButton),
  103. teamsTable,
  104. teamsRow: () => within(teamsTable()).getByRole('row', { name: /team one team.one@test\.com 2000/i }),
  105. orgsTable,
  106. orgsEditorRow: () => within(orgsTable()).getByRole('row', { name: /main editor current/i }),
  107. orgsViewerRow: () => within(orgsTable()).getByRole('row', { name: /second viewer select organisation/i }),
  108. orgsAdminRow: () => within(orgsTable()).getByRole('row', { name: /third admin select organisation/i }),
  109. sessionsTable,
  110. sessionsRow: () =>
  111. within(sessionsTable()).getByRole('row', {
  112. name: /now January 1, 2021 localhost chrome on mac os x 11/i,
  113. }),
  114. };
  115. }
  116. async function getTestContext(overrides: Partial<Props> = {}) {
  117. jest.clearAllMocks();
  118. const putSpy = jest.spyOn(backendSrv, 'put');
  119. const getSpy = jest
  120. .spyOn(backendSrv, 'get')
  121. .mockResolvedValue({ timezone: 'UTC', homeDashboardId: 0, theme: 'dark' });
  122. const searchSpy = jest.spyOn(backendSrv, 'search').mockResolvedValue([]);
  123. const props = { ...defaultProps, ...overrides };
  124. const { rerender } = render(
  125. <TestProvider>
  126. <UserProfileEditPage {...props} />
  127. </TestProvider>
  128. );
  129. await waitFor(() => expect(props.initUserProfilePage).toHaveBeenCalledTimes(1));
  130. return { rerender, putSpy, getSpy, searchSpy, props };
  131. }
  132. describe('UserProfileEditPage', () => {
  133. describe('when loading user', () => {
  134. it('should show loading placeholder', async () => {
  135. await getTestContext({ user: null });
  136. expect(screen.getByText(/loading \.\.\./i)).toBeInTheDocument();
  137. });
  138. });
  139. describe('when user has loaded', () => {
  140. it('should show edit profile form', async () => {
  141. await getTestContext();
  142. const { name, email, username, saveProfile } = getSelectors();
  143. expect(screen.getByText(/edit profile/i)).toBeInTheDocument();
  144. expect(name()).toBeInTheDocument();
  145. expect(name()).toHaveValue('Test User');
  146. expect(email()).toBeInTheDocument();
  147. expect(email()).toHaveValue('test@test.com');
  148. expect(username()).toBeInTheDocument();
  149. expect(username()).toHaveValue('test');
  150. expect(saveProfile()).toBeInTheDocument();
  151. });
  152. it('should show shared preferences', async () => {
  153. await getTestContext();
  154. const { dashboardSelect, dashboardValue, timepickerSelect, timepickerValue, savePreferences } = getSelectors();
  155. expect(screen.getByRole('group', { name: /preferences/i })).toBeInTheDocument();
  156. expect(screen.getByRole('radio', { name: /default/i })).toBeInTheDocument();
  157. expect(screen.getByRole('radio', { name: /dark/i })).toBeInTheDocument();
  158. expect(screen.getByRole('radio', { name: /light/i })).toBeInTheDocument();
  159. expect(dashboardSelect()).toBeInTheDocument();
  160. expect(dashboardValue()).toBeInTheDocument();
  161. expect(timepickerSelect()).toBeInTheDocument();
  162. expect(timepickerValue()).toBeInTheDocument();
  163. expect(savePreferences()).toBeInTheDocument();
  164. });
  165. describe('and teams are loading', () => {
  166. it('should show teams loading placeholder', async () => {
  167. await getTestContext({ teamsAreLoading: true });
  168. expect(screen.getByText(/loading teams\.\.\./i)).toBeInTheDocument();
  169. });
  170. });
  171. describe('and teams are loaded', () => {
  172. it('should show teams', async () => {
  173. await getTestContext();
  174. const { teamsTable, teamsRow } = getSelectors();
  175. expect(screen.getByRole('heading', { name: /teams/i })).toBeInTheDocument();
  176. expect(teamsTable()).toBeInTheDocument();
  177. expect(teamsRow()).toBeInTheDocument();
  178. });
  179. });
  180. describe('and organizations are loading', () => {
  181. it('should show teams loading placeholder', async () => {
  182. await getTestContext({ orgsAreLoading: true });
  183. expect(screen.getByText(/loading organizations\.\.\./i)).toBeInTheDocument();
  184. });
  185. });
  186. describe('and organizations are loaded', () => {
  187. it('should show organizations', async () => {
  188. await getTestContext();
  189. const { orgsTable, orgsEditorRow, orgsViewerRow, orgsAdminRow } = getSelectors();
  190. expect(screen.getByRole('heading', { name: /organizations/i })).toBeInTheDocument();
  191. expect(orgsTable()).toBeInTheDocument();
  192. expect(orgsEditorRow()).toBeInTheDocument();
  193. expect(orgsViewerRow()).toBeInTheDocument();
  194. expect(orgsAdminRow()).toBeInTheDocument();
  195. });
  196. });
  197. describe('and sessions are loading', () => {
  198. it('should show teams loading placeholder', async () => {
  199. await getTestContext({ sessionsAreLoading: true });
  200. expect(screen.getByText(/loading sessions\.\.\./i)).toBeInTheDocument();
  201. });
  202. });
  203. describe('and sessions are loaded', () => {
  204. it('should show sessions', async () => {
  205. await getTestContext();
  206. const { sessionsTable, sessionsRow } = getSelectors();
  207. expect(sessionsTable()).toBeInTheDocument();
  208. expect(sessionsRow()).toBeInTheDocument();
  209. });
  210. });
  211. describe('and user is edited and saved', () => {
  212. it('should call updateUserProfile', async () => {
  213. const { props } = await getTestContext();
  214. const { email, saveProfile } = getSelectors();
  215. await userEvent.clear(email());
  216. await userEvent.type(email(), 'test@test.se');
  217. // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed
  218. await userEvent.click(saveProfile(), { pointerEventsCheck: PointerEventsCheckLevel.Never });
  219. await waitFor(() => expect(props.updateUserProfile).toHaveBeenCalledTimes(1));
  220. expect(props.updateUserProfile).toHaveBeenCalledWith({
  221. email: 'test@test.se',
  222. login: 'test',
  223. name: 'Test User',
  224. });
  225. });
  226. });
  227. describe('and organization is changed', () => {
  228. it('should call changeUserOrg', async () => {
  229. const { props } = await getTestContext();
  230. const orgsAdminSelectButton = () =>
  231. within(getSelectors().orgsAdminRow()).getByRole('button', {
  232. name: /select organisation/i,
  233. });
  234. await userEvent.click(orgsAdminSelectButton());
  235. await waitFor(() => expect(props.changeUserOrg).toHaveBeenCalledTimes(1));
  236. expect(props.changeUserOrg).toHaveBeenCalledWith({
  237. name: 'Third',
  238. orgId: 2,
  239. role: 'Admin',
  240. });
  241. });
  242. });
  243. describe('and session is revoked', () => {
  244. it('should call revokeUserSession', async () => {
  245. const { props } = await getTestContext();
  246. const sessionsRevokeButton = () =>
  247. within(getSelectors().sessionsRow()).getByRole('button', {
  248. name: /revoke user session/i,
  249. });
  250. await userEvent.click(sessionsRevokeButton());
  251. await waitFor(() => expect(props.revokeUserSession).toHaveBeenCalledTimes(1));
  252. expect(props.revokeUserSession).toHaveBeenCalledWith(0);
  253. });
  254. });
  255. });
  256. });