merge_request_tabs_spec.js 15 KB
Newer Older
1
/* eslint-disable no-var, comma-dangle, object-shorthand */
2
/* global Notes */
Fatih Acet's avatar
Fatih Acet committed
3

4
import '~/merge_request_tabs';
Mike Greiling's avatar
Mike Greiling committed
5
import '~/commit/pipelines/pipelines_bundle';
6 7
import '~/breakpoints';
import '~/lib/utils/common_utils';
8
import Diff from '~/diff';
9
import '~/notes';
10
import 'vendor/jquery.scrollTo';
Fatih Acet's avatar
Fatih Acet committed
11

12 13 14 15 16
(function () {
  describe('MergeRequestTabs', function () {
    var stubLocation = {};
    var setLocation = function (stubs) {
      var defaults = {
Fatih Acet's avatar
Fatih Acet committed
17 18 19 20
        pathname: '',
        search: '',
        hash: ''
      };
21
      $.extend(stubLocation, defaults, stubs || {});
Fatih Acet's avatar
Fatih Acet committed
22
    };
23

24 25
    const inlineChangesTabJsonFixture = 'merge_request_diffs/inline_changes_tab_with_comments.json';
    const parallelChangesTabJsonFixture = 'merge_request_diffs/parallel_changes_tab_with_comments.json';
26 27 28 29 30 31
    preloadFixtures(
      'merge_requests/merge_request_with_task_list.html.raw',
      'merge_requests/diff_comment.html.raw',
      inlineChangesTabJsonFixture,
      parallelChangesTabJsonFixture
    );
32 33 34 35 36

    beforeEach(function () {
      this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
      setLocation();

37 38 39
      this.spies = {
        history: spyOn(window.history, 'replaceState').and.callFake(function () {})
      };
Fatih Acet's avatar
Fatih Acet committed
40
    });
41

42
    afterEach(function () {
Alfredo Sumaran's avatar
Alfredo Sumaran committed
43 44
      this.class.unbindEvents();
      this.class.destroyPipelinesView();
45 46
    });

47
    describe('activateTab', function () {
48
      beforeEach(function () {
Steffen Rauh's avatar
Steffen Rauh committed
49
        spyOn($, 'ajax').and.callFake(function () {});
50
        loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
51
        this.subject = this.class.activateTab;
Fatih Acet's avatar
Fatih Acet committed
52
      });
53
      it('shows the notes tab when action is show', function () {
Fatih Acet's avatar
Fatih Acet committed
54
        this.subject('show');
55
        expect($('#notes')).toHaveClass('active');
Fatih Acet's avatar
Fatih Acet committed
56
      });
57
      it('shows the commits tab when action is commits', function () {
Fatih Acet's avatar
Fatih Acet committed
58
        this.subject('commits');
59
        expect($('#commits')).toHaveClass('active');
Fatih Acet's avatar
Fatih Acet committed
60
      });
61
      it('shows the diffs tab when action is diffs', function () {
Fatih Acet's avatar
Fatih Acet committed
62
        this.subject('diffs');
63
        expect($('#diffs')).toHaveClass('active');
Fatih Acet's avatar
Fatih Acet committed
64 65
      });
    });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
66

67
    describe('opensInNewTab', function () {
68
      var tabUrl;
69
      var windowTarget = '_blank';
70

71
      beforeEach(function () {
72 73 74
        loadFixtures('merge_requests/merge_request_with_task_list.html.raw');

        tabUrl = $('.commits-tab a').attr('href');
75 76

        spyOn($.fn, 'attr').and.returnValue(tabUrl);
77
      });
78 79

      describe('meta click', () => {
80
        let metakeyEvent;
81
        beforeEach(function () {
82
          metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true });
83 84 85 86 87 88 89 90 91
        });

        it('opens page when commits link is clicked', function () {
          spyOn(window, 'open').and.callFake(function (url, name) {
            expect(url).toEqual(tabUrl);
            expect(name).toEqual(windowTarget);
          });

          this.class.bindEvents();
92
          $('.merge-request-tabs .commits-tab a').trigger(metakeyEvent);
93 94 95 96 97 98 99 100 101
        });

        it('opens page when commits badge is clicked', function () {
          spyOn(window, 'open').and.callFake(function (url, name) {
            expect(url).toEqual(tabUrl);
            expect(name).toEqual(windowTarget);
          });

          this.class.bindEvents();
102
          $('.merge-request-tabs .commits-tab a .badge').trigger(metakeyEvent);
103 104 105
        });
      });

106
      it('opens page tab in a new browser tab with Ctrl+Click - Windows/Linux', function () {
107 108
        spyOn(window, 'open').and.callFake(function (url, name) {
          expect(url).toEqual(tabUrl);
109
          expect(name).toEqual(windowTarget);
110
        });
111

112 113 114 115 116 117 118
        this.class.clickTab({
          metaKey: false,
          ctrlKey: true,
          which: 1,
          stopImmediatePropagation: function () {}
        });
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
119

120
      it('opens page tab in a new browser tab with Cmd+Click - Mac', function () {
121 122
        spyOn(window, 'open').and.callFake(function (url, name) {
          expect(url).toEqual(tabUrl);
123
          expect(name).toEqual(windowTarget);
124 125
        });

126 127 128 129 130 131 132
        this.class.clickTab({
          metaKey: true,
          ctrlKey: false,
          which: 1,
          stopImmediatePropagation: function () {}
        });
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
133

134
      it('opens page tab in a new browser tab with Middle-click - Mac/PC', function () {
135 136
        spyOn(window, 'open').and.callFake(function (url, name) {
          expect(url).toEqual(tabUrl);
137
          expect(name).toEqual(windowTarget);
138 139
        });

140 141 142 143 144 145 146
        this.class.clickTab({
          metaKey: false,
          ctrlKey: false,
          which: 2,
          stopImmediatePropagation: function () {}
        });
      });
147
    });
148

149
    describe('setCurrentAction', function () {
150
      beforeEach(function () {
Steffen Rauh's avatar
Steffen Rauh committed
151
        spyOn($, 'ajax').and.callFake(function () {});
152
        this.subject = this.class.setCurrentAction;
Fatih Acet's avatar
Fatih Acet committed
153
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
154

155 156
      it('changes from commits', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
157 158
          pathname: '/foo/bar/merge_requests/1/commits'
        });
159
        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
160
        expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
Fatih Acet's avatar
Fatih Acet committed
161
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
162

163 164
      it('changes from diffs', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
165 166
          pathname: '/foo/bar/merge_requests/1/diffs'
        });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
167

168
        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
169
        expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
Fatih Acet's avatar
Fatih Acet committed
170
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
171

172 173
      it('changes from diffs.html', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
174 175
          pathname: '/foo/bar/merge_requests/1/diffs.html'
        });
176
        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
177
        expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
Fatih Acet's avatar
Fatih Acet committed
178
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
179

180 181
      it('changes from notes', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
182 183 184
          pathname: '/foo/bar/merge_requests/1'
        });
        expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs');
185
        expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits');
Fatih Acet's avatar
Fatih Acet committed
186
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
187

188 189
      it('includes search parameters and hash string', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
190 191 192 193
          pathname: '/foo/bar/merge_requests/1/diffs',
          search: '?view=parallel',
          hash: '#L15-35'
        });
194
        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35');
Fatih Acet's avatar
Fatih Acet committed
195
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
196

197 198 199
      it('replaces the current history state', function () {
        var newState;
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
200 201
          pathname: '/foo/bar/merge_requests/1'
        });
202
        newState = this.subject('commits');
203 204 205
        expect(this.spies.history).toHaveBeenCalledWith({
          url: newState
        }, document.title, newState);
Fatih Acet's avatar
Fatih Acet committed
206
      });
Alfredo Sumaran's avatar
Alfredo Sumaran committed
207

208 209
      it('treats "show" like "notes"', function () {
        setLocation({
Fatih Acet's avatar
Fatih Acet committed
210 211
          pathname: '/foo/bar/merge_requests/1/commits'
        });
212
        expect(this.subject('show')).toBe('/foo/bar/merge_requests/1');
Fatih Acet's avatar
Fatih Acet committed
213 214
      });
    });
215

216
    describe('tabShown', () => {
217
      beforeEach(function () {
218 219 220
        spyOn($, 'ajax').and.callFake(function (options) {
          options.success({ html: '' });
        });
221 222 223 224 225 226
        loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
      });

      describe('with "Side-by-side"/parallel diff view', () => {
        beforeEach(function () {
          this.class.diffViewType = () => 'parallel';
227
          Diff.prototype.diffViewType = () => 'parallel';
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        });

        it('maintains `container-limited` for pipelines tab', function (done) {
          const asyncClick = function (selector) {
            return new Promise((resolve) => {
              setTimeout(() => {
                document.querySelector(selector).click();
                resolve();
              });
            });
          };
          asyncClick('.merge-request-tabs .pipelines-tab a')
            .then(() => asyncClick('.merge-request-tabs .diffs-tab a'))
            .then(() => asyncClick('.merge-request-tabs .pipelines-tab a'))
            .then(() => {
              const hasContainerLimitedClass = document.querySelector('.content-wrapper .container-fluid').classList.contains('container-limited');
              expect(hasContainerLimitedClass).toBe(true);
            })
            .then(done)
            .catch((err) => {
              done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`);
            });
        });
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

        it('maintains `container-limited` when switching from "Changes" tab before it loads', function (done) {
          const asyncClick = function (selector) {
            return new Promise((resolve) => {
              setTimeout(() => {
                document.querySelector(selector).click();
                resolve();
              });
            });
          };

          asyncClick('.merge-request-tabs .diffs-tab a')
            .then(() => asyncClick('.merge-request-tabs .notes-tab a'))
            .then(() => {
              const hasContainerLimitedClass = document.querySelector('.content-wrapper .container-fluid').classList.contains('container-limited');
              expect(hasContainerLimitedClass).toBe(true);
            })
            .then(done)
            .catch((err) => {
              done.fail(`Something went wrong clicking MR tabs: ${err.message}\n${err.stack}`);
            });
        });
273 274 275
      });
    });

276
    describe('loadDiff', function () {
277 278
      beforeEach(() => {
        loadFixtures('merge_requests/diff_comment.html.raw');
279
        $('body').attr('data-page', 'projects:merge_requests:show');
280 281 282 283 284 285 286 287
        window.gl.ImageFile = () => {};
        window.notes = new Notes('', []);
        spyOn(window.notes, 'toggleDiffNote').and.callThrough();
      });

      afterEach(() => {
        delete window.gl.ImageFile;
        delete window.notes;
288 289 290

        // Undo what we did to the shared <body>
        $('body').removeAttr('data-page');
291 292
      });

Steffen Rauh's avatar
Steffen Rauh committed
293 294
      it('requires an absolute pathname', function () {
        spyOn($, 'ajax').and.callFake(function (options) {
295 296
          expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
        });
297

298 299
        this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
      });
300 301 302 303 304 305 306 307 308 309 310

      it('triggers scroll event when diff already loaded', function () {
        spyOn(document, 'dispatchEvent');

        this.class.diffsLoaded = true;
        this.class.loadDiff('/foo/bar/merge_requests/1/diffs');

        expect(
          document.dispatchEvent,
        ).toHaveBeenCalledWith(new CustomEvent('scroll'));
      });
311

312 313 314 315
      describe('with inline diff', () => {
        let noteId;
        let noteLineNumId;

316
        beforeEach(() => {
317 318 319 320 321 322 323 324 325 326 327
          const diffsResponse = getJSONFixture(inlineChangesTabJsonFixture);

          const $html = $(diffsResponse.html);
          noteId = $html.find('.note').attr('id');
          noteLineNumId = $html
            .find('.note')
            .closest('.notes_holder')
            .prev('.line_holder')
            .find('a[data-linenumber]')
            .attr('href')
            .replace('#', '');
328

329 330 331
          spyOn($, 'ajax').and.callFake(function (options) {
            options.success(diffsResponse);
          });
332 333
        });

334 335 336 337 338 339 340 341 342 343 344
        describe('with note fragment hash', () => {
          it('should expand and scroll to linked fragment hash #note_xxx', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');

            expect(noteId.length).toBeGreaterThan(0);
            expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
              target: jasmine.any(Object),
              lineType: 'old',
              forceShow: true,
            });
345 346
          });

347 348 349
          it('should gracefully ignore non-existant fragment hash', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
350

351
            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
352 353 354
          });
        });

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
        describe('with line number fragment hash', () => {
          it('should gracefully ignore line number fragment hash', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteLineNumId);
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');

            expect(noteLineNumId.length).toBeGreaterThan(0);
            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
          });
        });
      });

      describe('with parallel diff', () => {
        let noteId;
        let noteLineNumId;

        beforeEach(() => {
          const diffsResponse = getJSONFixture(parallelChangesTabJsonFixture);

          const $html = $(diffsResponse.html);
          noteId = $html.find('.note').attr('id');
          noteLineNumId = $html
            .find('.note')
            .closest('.notes_holder')
            .prev('.line_holder')
            .find('a[data-linenumber]')
            .attr('href')
            .replace('#', '');

383
          spyOn($, 'ajax').and.callFake(function (options) {
384
            options.success(diffsResponse);
385
          });
386 387 388 389 390
        });

        describe('with note fragment hash', () => {
          it('should expand and scroll to linked fragment hash #note_xxx', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
391

392
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
393

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
            expect(noteId.length).toBeGreaterThan(0);
            expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
              target: jasmine.any(Object),
              lineType: 'new',
              forceShow: true,
            });
          });

          it('should gracefully ignore non-existant fragment hash', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');

            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
          });
        });

        describe('with line number fragment hash', () => {
          it('should gracefully ignore line number fragment hash', function () {
            spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteLineNumId);
            this.class.loadDiff('/foo/bar/merge_requests/1/diffs');

            expect(noteLineNumId.length).toBeGreaterThan(0);
            expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
          });
418 419
        });
      });
420
    });
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

    describe('expandViewContainer', function () {
      beforeEach(() => {
        $('body').append('<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>');
      });

      afterEach(() => {
        $('.content-wrapper').remove();
      });

      it('removes container-limited from containers', function () {
        this.class.expandViewContainer();

        expect($('.content-wrapper')).not.toContainElement('.container-limited');
      });

      it('does remove container-limited from breadcrumbs', function () {
        $('.container-limited').addClass('breadcrumbs');
        this.class.expandViewContainer();

        expect($('.content-wrapper')).toContainElement('.container-limited');
      });
    });
Fatih Acet's avatar
Fatih Acet committed
444
  });
445
}).call(window);