PageRenderTime 62ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/frontend/ide/stores/modules/commit/actions_spec.js

https://gitlab.com/523/gitlab-ce
JavaScript | 484 lines | 412 code | 72 blank | 0 comment | 3 complexity | 3700f3c8478cf817d661d0aae2c24c79 MD5 | raw file
  1. import axios from 'axios';
  2. import MockAdapter from 'axios-mock-adapter';
  3. import testAction from 'helpers/vuex_action_helper';
  4. import { file } from 'jest/ide/helpers';
  5. import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';
  6. import eventHub from '~/ide/eventhub';
  7. import { createRouter } from '~/ide/ide_router';
  8. import { createUnexpectedCommitError } from '~/ide/lib/errors';
  9. import service from '~/ide/services';
  10. import { createStore } from '~/ide/stores';
  11. import * as actions from '~/ide/stores/modules/commit/actions';
  12. import {
  13. COMMIT_TO_CURRENT_BRANCH,
  14. COMMIT_TO_NEW_BRANCH,
  15. } from '~/ide/stores/modules/commit/constants';
  16. import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
  17. import { visitUrl } from '~/lib/utils/url_utility';
  18. jest.mock('~/lib/utils/url_utility', () => ({
  19. ...jest.requireActual('~/lib/utils/url_utility'),
  20. visitUrl: jest.fn(),
  21. }));
  22. const TEST_COMMIT_SHA = '123456789';
  23. const COMMIT_RESPONSE = {
  24. id: '123456',
  25. short_id: '123',
  26. message: 'test message',
  27. committed_date: 'date',
  28. parent_ids: [],
  29. stats: {
  30. additions: '1',
  31. deletions: '2',
  32. },
  33. };
  34. describe('IDE commit module actions', () => {
  35. let mock;
  36. let store;
  37. let router;
  38. beforeEach(() => {
  39. store = createStore();
  40. router = createRouter(store);
  41. gon.api_version = 'v1';
  42. mock = new MockAdapter(axios);
  43. jest.spyOn(router, 'push').mockImplementation();
  44. mock
  45. .onGet('/api/v1/projects/abcproject/repository/branches/main')
  46. .reply(200, { commit: COMMIT_RESPONSE });
  47. });
  48. afterEach(() => {
  49. delete gon.api_version;
  50. mock.restore();
  51. });
  52. describe('updateCommitMessage', () => {
  53. it('updates store with new commit message', async () => {
  54. await store.dispatch('commit/updateCommitMessage', 'testing');
  55. expect(store.state.commit.commitMessage).toBe('testing');
  56. });
  57. });
  58. describe('discardDraft', () => {
  59. it('resets commit message to blank', async () => {
  60. store.state.commit.commitMessage = 'testing';
  61. await store.dispatch('commit/discardDraft');
  62. expect(store.state.commit.commitMessage).not.toBe('testing');
  63. });
  64. });
  65. describe('updateCommitAction', () => {
  66. it('updates store with new commit action', async () => {
  67. await store.dispatch('commit/updateCommitAction', '1');
  68. expect(store.state.commit.commitAction).toBe('1');
  69. });
  70. });
  71. describe('updateBranchName', () => {
  72. let originalGon;
  73. beforeEach(() => {
  74. originalGon = window.gon;
  75. window.gon = { current_username: 'johndoe' };
  76. store.state.currentBranchId = 'main';
  77. });
  78. afterEach(() => {
  79. window.gon = originalGon;
  80. });
  81. it('updates store with new branch name', async () => {
  82. await store.dispatch('commit/updateBranchName', 'branch-name');
  83. expect(store.state.commit.newBranchName).toBe('branch-name');
  84. });
  85. });
  86. describe('addSuffixToBranchName', () => {
  87. it('adds suffix to branchName', async () => {
  88. jest.spyOn(Math, 'random').mockReturnValue(0.391352525);
  89. store.state.commit.newBranchName = 'branch-name';
  90. await store.dispatch('commit/addSuffixToBranchName');
  91. expect(store.state.commit.newBranchName).toBe('branch-name-39135');
  92. });
  93. });
  94. describe('setLastCommitMessage', () => {
  95. beforeEach(() => {
  96. Object.assign(store.state, {
  97. currentProjectId: 'abcproject',
  98. projects: {
  99. abcproject: {
  100. web_url: 'http://testing',
  101. },
  102. },
  103. });
  104. });
  105. it('updates commit message with short_id', async () => {
  106. await store.dispatch('commit/setLastCommitMessage', { short_id: '123' });
  107. expect(store.state.lastCommitMsg).toContain(
  108. 'Your changes have been committed. Commit <a href="http://testing/-/commit/123" class="commit-sha">123</a>',
  109. );
  110. });
  111. it('updates commit message with stats', async () => {
  112. await store.dispatch('commit/setLastCommitMessage', {
  113. short_id: '123',
  114. stats: {
  115. additions: '1',
  116. deletions: '2',
  117. },
  118. });
  119. expect(store.state.lastCommitMsg).toBe(
  120. 'Your changes have been committed. Commit <a href="http://testing/-/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.',
  121. );
  122. });
  123. });
  124. describe('updateFilesAfterCommit', () => {
  125. const data = {
  126. id: '123',
  127. message: 'testing commit message',
  128. committed_date: '123',
  129. committer_name: 'root',
  130. };
  131. const branch = 'main';
  132. let f;
  133. beforeEach(() => {
  134. jest.spyOn(eventHub, '$emit').mockImplementation();
  135. f = file('changedFile');
  136. Object.assign(f, {
  137. active: true,
  138. changed: true,
  139. content: 'file content',
  140. });
  141. Object.assign(store.state, {
  142. currentProjectId: 'abcproject',
  143. currentBranchId: 'main',
  144. projects: {
  145. abcproject: {
  146. web_url: 'web_url',
  147. branches: {
  148. main: {
  149. workingReference: '',
  150. commit: {
  151. short_id: TEST_COMMIT_SHA,
  152. },
  153. },
  154. },
  155. },
  156. },
  157. stagedFiles: [
  158. f,
  159. {
  160. ...file('changedFile2'),
  161. changed: true,
  162. },
  163. ],
  164. });
  165. store.state.openFiles = store.state.stagedFiles;
  166. store.state.stagedFiles.forEach((stagedFile) => {
  167. store.state.entries[stagedFile.path] = stagedFile;
  168. });
  169. });
  170. it('updates stores working reference', async () => {
  171. await store.dispatch('commit/updateFilesAfterCommit', {
  172. data,
  173. branch,
  174. });
  175. expect(store.state.projects.abcproject.branches.main.workingReference).toBe(data.id);
  176. });
  177. it('resets all files changed status', async () => {
  178. await store.dispatch('commit/updateFilesAfterCommit', {
  179. data,
  180. branch,
  181. });
  182. store.state.openFiles.forEach((entry) => {
  183. expect(entry.changed).toBeFalsy();
  184. });
  185. });
  186. it('sets files commit data', async () => {
  187. await store.dispatch('commit/updateFilesAfterCommit', {
  188. data,
  189. branch,
  190. });
  191. expect(f.lastCommitSha).toBe(data.id);
  192. });
  193. it('updates raw content for changed file', async () => {
  194. await store.dispatch('commit/updateFilesAfterCommit', {
  195. data,
  196. branch,
  197. });
  198. expect(f.raw).toBe(f.content);
  199. });
  200. it('emits changed event for file', async () => {
  201. await store.dispatch('commit/updateFilesAfterCommit', {
  202. data,
  203. branch,
  204. });
  205. expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.content.${f.key}`, {
  206. content: f.content,
  207. changed: false,
  208. });
  209. });
  210. });
  211. describe('commitChanges', () => {
  212. beforeEach(() => {
  213. document.body.innerHTML += '<div class="flash-container"></div>';
  214. const f = {
  215. ...file('changed'),
  216. type: 'blob',
  217. active: true,
  218. lastCommitSha: TEST_COMMIT_SHA,
  219. content: '\n',
  220. raw: '\n',
  221. };
  222. Object.assign(store.state, {
  223. stagedFiles: [f],
  224. changedFiles: [f],
  225. openFiles: [f],
  226. currentProjectId: 'abcproject',
  227. currentBranchId: 'main',
  228. projects: {
  229. abcproject: {
  230. default_branch: 'main',
  231. web_url: 'webUrl',
  232. branches: {
  233. main: {
  234. name: 'main',
  235. workingReference: '1',
  236. commit: {
  237. id: TEST_COMMIT_SHA,
  238. },
  239. can_push: true,
  240. },
  241. },
  242. userPermissions: {
  243. [PERMISSION_CREATE_MR]: true,
  244. },
  245. },
  246. },
  247. });
  248. store.state.commit.commitAction = '2';
  249. store.state.commit.commitMessage = 'testing 123';
  250. store.state.openFiles.forEach((localF) => {
  251. store.state.entries[localF.path] = localF;
  252. });
  253. });
  254. afterEach(() => {
  255. document.querySelector('.flash-container').remove();
  256. });
  257. describe('success', () => {
  258. beforeEach(() => {
  259. jest.spyOn(service, 'commit').mockResolvedValue({ data: COMMIT_RESPONSE });
  260. });
  261. it('calls service', async () => {
  262. await store.dispatch('commit/commitChanges');
  263. expect(service.commit).toHaveBeenCalledWith('abcproject', {
  264. branch: expect.anything(),
  265. commit_message: 'testing 123',
  266. actions: [
  267. {
  268. action: commitActionTypes.update,
  269. file_path: expect.anything(),
  270. content: '\n',
  271. encoding: expect.anything(),
  272. last_commit_id: undefined,
  273. previous_path: undefined,
  274. },
  275. ],
  276. start_sha: TEST_COMMIT_SHA,
  277. });
  278. });
  279. it('sends lastCommit ID when not creating new branch', async () => {
  280. store.state.commit.commitAction = '1';
  281. await store.dispatch('commit/commitChanges');
  282. expect(service.commit).toHaveBeenCalledWith('abcproject', {
  283. branch: expect.anything(),
  284. commit_message: 'testing 123',
  285. actions: [
  286. {
  287. action: commitActionTypes.update,
  288. file_path: expect.anything(),
  289. content: '\n',
  290. encoding: expect.anything(),
  291. last_commit_id: TEST_COMMIT_SHA,
  292. previous_path: undefined,
  293. },
  294. ],
  295. start_sha: undefined,
  296. });
  297. });
  298. it('sets last Commit Msg', async () => {
  299. await store.dispatch('commit/commitChanges');
  300. expect(store.state.lastCommitMsg).toBe(
  301. 'Your changes have been committed. Commit <a href="webUrl/-/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.',
  302. );
  303. });
  304. it('adds commit data to files', async () => {
  305. await store.dispatch('commit/commitChanges');
  306. expect(store.state.entries[store.state.openFiles[0].path].lastCommitSha).toBe(
  307. COMMIT_RESPONSE.id,
  308. );
  309. });
  310. it('resets stores commit actions', async () => {
  311. store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
  312. await store.dispatch('commit/commitChanges');
  313. expect(store.state.commit.commitAction).not.toBe(COMMIT_TO_NEW_BRANCH);
  314. });
  315. it('removes all staged files', async () => {
  316. await store.dispatch('commit/commitChanges');
  317. expect(store.state.stagedFiles.length).toBe(0);
  318. });
  319. describe('merge request', () => {
  320. it('redirects to new merge request page', async () => {
  321. jest.spyOn(eventHub, '$on').mockImplementation();
  322. store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
  323. store.state.commit.shouldCreateMR = true;
  324. await store.dispatch('commit/commitChanges');
  325. expect(visitUrl).toHaveBeenCalledWith(
  326. `webUrl/-/merge_requests/new?merge_request[source_branch]=${store.getters['commit/placeholderBranchName']}&merge_request[target_branch]=main&nav_source=webide`,
  327. );
  328. });
  329. it('does not redirect to new merge request page when shouldCreateMR is not checked', async () => {
  330. jest.spyOn(eventHub, '$on').mockImplementation();
  331. store.state.commit.commitAction = COMMIT_TO_NEW_BRANCH;
  332. store.state.commit.shouldCreateMR = false;
  333. await store.dispatch('commit/commitChanges');
  334. expect(visitUrl).not.toHaveBeenCalled();
  335. });
  336. it('does not redirect to merge request page if shouldCreateMR is checked, but branch is the default branch', async () => {
  337. jest.spyOn(eventHub, '$on').mockImplementation();
  338. store.state.commit.commitAction = COMMIT_TO_CURRENT_BRANCH;
  339. store.state.commit.shouldCreateMR = true;
  340. await store.dispatch('commit/commitChanges');
  341. expect(visitUrl).not.toHaveBeenCalled();
  342. });
  343. it('resets changed files before redirecting', () => {
  344. jest.spyOn(eventHub, '$on').mockImplementation();
  345. store.state.commit.commitAction = '3';
  346. return store.dispatch('commit/commitChanges').then(() => {
  347. expect(store.state.stagedFiles.length).toBe(0);
  348. });
  349. });
  350. });
  351. });
  352. describe('success response with failed message', () => {
  353. beforeEach(() => {
  354. jest.spyOn(service, 'commit').mockResolvedValue({
  355. data: {
  356. message: 'failed message',
  357. },
  358. });
  359. });
  360. it('shows failed message', async () => {
  361. await store.dispatch('commit/commitChanges');
  362. const alert = document.querySelector('.flash-container');
  363. expect(alert.textContent.trim()).toBe('failed message');
  364. });
  365. });
  366. describe('failed response', () => {
  367. beforeEach(() => {
  368. jest.spyOn(service, 'commit').mockRejectedValue({});
  369. });
  370. it('commits error updates', async () => {
  371. jest.spyOn(store, 'commit');
  372. await store.dispatch('commit/commitChanges').catch(() => {});
  373. expect(store.commit.mock.calls).toEqual([
  374. ['commit/CLEAR_ERROR', undefined, undefined],
  375. ['commit/UPDATE_LOADING', true, undefined],
  376. ['commit/UPDATE_LOADING', false, undefined],
  377. ['commit/SET_ERROR', createUnexpectedCommitError(), undefined],
  378. ]);
  379. });
  380. });
  381. describe('first commit of a branch', () => {
  382. it('commits TOGGLE_EMPTY_STATE mutation on empty repo', async () => {
  383. jest.spyOn(service, 'commit').mockResolvedValue({ data: COMMIT_RESPONSE });
  384. jest.spyOn(store, 'commit');
  385. await store.dispatch('commit/commitChanges');
  386. expect(store.commit.mock.calls).toEqual(
  387. expect.arrayContaining([['TOGGLE_EMPTY_STATE', expect.any(Object), expect.any(Object)]]),
  388. );
  389. });
  390. it('does not commmit TOGGLE_EMPTY_STATE mutation on existing project', async () => {
  391. COMMIT_RESPONSE.parent_ids.push('1234');
  392. jest.spyOn(service, 'commit').mockResolvedValue({ data: COMMIT_RESPONSE });
  393. jest.spyOn(store, 'commit');
  394. await store.dispatch('commit/commitChanges');
  395. expect(store.commit.mock.calls).not.toEqual(
  396. expect.arrayContaining([['TOGGLE_EMPTY_STATE', expect.any(Object), expect.any(Object)]]),
  397. );
  398. });
  399. });
  400. });
  401. describe('toggleShouldCreateMR', () => {
  402. it('commits both toggle and interacting with MR checkbox actions', () => {
  403. return testAction(
  404. actions.toggleShouldCreateMR,
  405. {},
  406. store.state,
  407. [{ type: mutationTypes.TOGGLE_SHOULD_CREATE_MR }],
  408. [],
  409. );
  410. });
  411. });
  412. });