You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssuesAppGuides-it.tsx 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import userEvent from '@testing-library/user-event';
  21. import React from 'react';
  22. import { mockCurrentUser } from '../../../helpers/testMocks';
  23. import { IssueTransition } from '../../../types/issues';
  24. import { NoticeType } from '../../../types/users';
  25. import {
  26. branchHandler,
  27. componentsHandler,
  28. issuesHandler,
  29. renderIssueApp,
  30. renderProjectIssuesApp,
  31. ui,
  32. usersHandler,
  33. } from '../test-utils';
  34. jest.mock('../sidebar/Sidebar', () => {
  35. const fakeSidebar = () => {
  36. return <div data-guiding-id="issue-5" />;
  37. };
  38. return {
  39. __esModule: true,
  40. default: fakeSidebar,
  41. Sidebar: fakeSidebar,
  42. };
  43. });
  44. jest.mock('../../../components/common/ScreenPositionHelper', () => ({
  45. __esModule: true,
  46. default: class ScreenPositionHelper extends React.Component<{
  47. children: (args: { top: number }) => React.ReactNode;
  48. }> {
  49. render() {
  50. // eslint-disable-next-line testing-library/no-node-access
  51. return this.props.children({ top: 10 });
  52. }
  53. },
  54. }));
  55. describe('issue guides', () => {
  56. beforeEach(() => {
  57. issuesHandler.reset();
  58. componentsHandler.reset();
  59. branchHandler.reset();
  60. usersHandler.reset();
  61. window.scrollTo = jest.fn();
  62. window.HTMLElement.prototype.scrollTo = jest.fn();
  63. });
  64. describe('Issue Guide', () => {
  65. it('should display guide', async () => {
  66. const user = userEvent.setup();
  67. renderIssueApp(
  68. mockCurrentUser({
  69. isLoggedIn: true,
  70. dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true },
  71. }),
  72. );
  73. expect(await ui.guidePopup.find()).toBeInTheDocument();
  74. expect(await ui.guidePopup.find()).toBeInTheDocument();
  75. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.title');
  76. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.content');
  77. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5');
  78. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  79. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.2.title');
  80. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.2.content');
  81. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.2.5');
  82. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  83. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.3.title');
  84. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.3.content');
  85. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.3.5');
  86. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  87. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.4.title');
  88. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.4.content');
  89. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.4.5');
  90. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  91. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.5.title');
  92. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.5.content');
  93. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.5.5');
  94. expect(ui.guidePopup.byRole('button', { name: 'Next' }).query()).not.toBeInTheDocument();
  95. await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get());
  96. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  97. });
  98. it('should not show guide for those who dismissed it', async () => {
  99. renderIssueApp(
  100. mockCurrentUser({
  101. isLoggedIn: true,
  102. dismissedNotices: {
  103. [NoticeType.ISSUE_GUIDE]: true,
  104. [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true,
  105. },
  106. }),
  107. );
  108. expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0);
  109. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  110. });
  111. it('should skip guide', async () => {
  112. const user = userEvent.setup();
  113. renderIssueApp(
  114. mockCurrentUser({
  115. isLoggedIn: true,
  116. dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true },
  117. }),
  118. );
  119. expect(await ui.guidePopup.find()).toBeInTheDocument();
  120. expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.title');
  121. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5');
  122. await user.click(ui.guidePopup.byRole('button', { name: 'skip' }).get());
  123. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  124. });
  125. it('should not show guide if issues need sync', async () => {
  126. renderProjectIssuesApp(
  127. undefined,
  128. { needIssueSync: true },
  129. mockCurrentUser({
  130. isLoggedIn: true,
  131. dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true },
  132. }),
  133. );
  134. expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0);
  135. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  136. });
  137. it('should not show guide if user is not logged in', async () => {
  138. renderIssueApp(mockCurrentUser({ isLoggedIn: false }));
  139. expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0);
  140. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  141. });
  142. it('should not show guide if there are no issues', () => {
  143. issuesHandler.setIssueList([]);
  144. renderIssueApp(mockCurrentUser({ isLoggedIn: true }));
  145. expect(ui.loading.query()).not.toBeInTheDocument();
  146. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  147. });
  148. it('should show guide on issue page and save its step on opened issue', async () => {
  149. const user = userEvent.setup();
  150. renderProjectIssuesApp('project/issues', undefined, mockCurrentUser({ isLoggedIn: true }));
  151. expect(await ui.guidePopup.find()).toBeInTheDocument();
  152. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5');
  153. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  154. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.2.5');
  155. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  156. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.3.5');
  157. await user.click(ui.issueItemAction1.get());
  158. expect(await ui.guidePopup.find()).toHaveTextContent('guiding.step_x_of_y.3.5');
  159. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  160. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.4.5');
  161. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  162. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.5.5');
  163. expect(ui.guidePopup.byRole('button', { name: 'Next' }).query()).not.toBeInTheDocument();
  164. await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get());
  165. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  166. });
  167. });
  168. describe('Issue new status and transition guide', () => {
  169. it('should save transition guide step', async () => {
  170. const user = userEvent.setup();
  171. issuesHandler.list[0].issue.transitions = [
  172. IssueTransition.Accept,
  173. IssueTransition.Confirm,
  174. IssueTransition.Resolve,
  175. IssueTransition.FalsePositive,
  176. IssueTransition.WontFix,
  177. ];
  178. renderProjectIssuesApp(
  179. 'project/issues',
  180. undefined,
  181. mockCurrentUser({ isLoggedIn: true, dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } }),
  182. );
  183. expect(await ui.guidePopup.find()).toBeInTheDocument();
  184. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  185. expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.2.3');
  186. await user.click(ui.issueItemAction1.get());
  187. expect(await ui.guidePopup.find()).toHaveTextContent('guiding.step_x_of_y.2.3');
  188. await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get());
  189. await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get());
  190. expect(ui.guidePopup.query()).not.toBeInTheDocument();
  191. });
  192. });
  193. });