summaryrefslogtreecommitdiff
path: root/spec/frontend/behaviors
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/frontend/behaviors
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
downloadgitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/frontend/behaviors')
-rw-r--r--spec/frontend/behaviors/autosize_spec.js20
-rw-r--r--spec/frontend/behaviors/bind_in_out_spec.js10
-rw-r--r--spec/frontend/behaviors/copy_as_gfm_spec.js125
-rw-r--r--spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js52
-rw-r--r--spec/frontend/behaviors/markdown/highlight_current_user_spec.js55
-rw-r--r--spec/frontend/behaviors/requires_input_spec.js62
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js322
7 files changed, 638 insertions, 8 deletions
diff --git a/spec/frontend/behaviors/autosize_spec.js b/spec/frontend/behaviors/autosize_spec.js
new file mode 100644
index 00000000000..59abae479d4
--- /dev/null
+++ b/spec/frontend/behaviors/autosize_spec.js
@@ -0,0 +1,20 @@
+import $ from 'jquery';
+import '~/behaviors/autosize';
+
+function load() {
+ $(document).trigger('load');
+}
+
+describe('Autosize behavior', () => {
+ beforeEach(() => {
+ setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
+ });
+
+ it('does not overwrite the resize property', () => {
+ load();
+
+ expect($('textarea')).toHaveCss({
+ resize: 'vertical',
+ });
+ });
+});
diff --git a/spec/frontend/behaviors/bind_in_out_spec.js b/spec/frontend/behaviors/bind_in_out_spec.js
index 923b6d372dd..92a68ddd387 100644
--- a/spec/frontend/behaviors/bind_in_out_spec.js
+++ b/spec/frontend/behaviors/bind_in_out_spec.js
@@ -163,14 +163,8 @@ describe('BindInOut', () => {
describe('init', () => {
beforeEach(() => {
- // eslint-disable-next-line func-names
- jest.spyOn(BindInOut.prototype, 'addEvents').mockImplementation(function() {
- return this;
- });
- // eslint-disable-next-line func-names
- jest.spyOn(BindInOut.prototype, 'updateOut').mockImplementation(function() {
- return this;
- });
+ jest.spyOn(BindInOut.prototype, 'addEvents').mockReturnThis();
+ jest.spyOn(BindInOut.prototype, 'updateOut').mockReturnThis();
testContext.init = BindInOut.init({}, {});
});
diff --git a/spec/frontend/behaviors/copy_as_gfm_spec.js b/spec/frontend/behaviors/copy_as_gfm_spec.js
new file mode 100644
index 00000000000..cf96ac488a8
--- /dev/null
+++ b/spec/frontend/behaviors/copy_as_gfm_spec.js
@@ -0,0 +1,125 @@
+import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
+
+describe('CopyAsGFM', () => {
+ describe('CopyAsGFM.pasteGFM', () => {
+ function callPasteGFM() {
+ const e = {
+ originalEvent: {
+ clipboardData: {
+ getData(mimeType) {
+ // When GFM code is copied, we put the regular plain text
+ // on the clipboard as `text/plain`, and the GFM as `text/x-gfm`.
+ // This emulates the behavior of `getData` with that data.
+ if (mimeType === 'text/plain') {
+ return 'code';
+ }
+ if (mimeType === 'text/x-gfm') {
+ return '`code`';
+ }
+ return null;
+ },
+ },
+ },
+ preventDefault() {},
+ };
+
+ CopyAsGFM.pasteGFM(e);
+ }
+
+ it('wraps pasted code when not already in code tags', () => {
+ jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ const insertedText = textFunc('This is code: ', '');
+
+ expect(insertedText).toEqual('`code`');
+ });
+
+ callPasteGFM();
+ });
+
+ it('does not wrap pasted code when already in code tags', () => {
+ jest.spyOn(window.gl.utils, 'insertText').mockImplementation((el, textFunc) => {
+ const insertedText = textFunc('This is code: `', '`');
+
+ expect(insertedText).toEqual('code');
+ });
+
+ callPasteGFM();
+ });
+ });
+
+ describe('CopyAsGFM.copyGFM', () => {
+ // Stub getSelection to return a purpose-built object.
+ const stubSelection = (html, parentNode) => ({
+ getRangeAt: () => ({
+ commonAncestorContainer: { tagName: parentNode },
+ cloneContents: () => {
+ const fragment = document.createDocumentFragment();
+ const node = document.createElement('div');
+ node.innerHTML = html;
+ Array.from(node.childNodes).forEach(item => fragment.appendChild(item));
+ return fragment;
+ },
+ }),
+ rangeCount: 1,
+ });
+
+ const clipboardData = {
+ setData() {},
+ };
+
+ const simulateCopy = () => {
+ const e = {
+ originalEvent: {
+ clipboardData,
+ },
+ preventDefault() {},
+ stopPropagation() {},
+ };
+ CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
+ return clipboardData;
+ };
+
+ beforeAll(done => {
+ initCopyAsGFM();
+
+ // Fake call to nodeToGfm so the import of lazy bundle happened
+ CopyAsGFM.nodeToGFM(document.createElement('div'))
+ .then(() => {
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ beforeEach(() => jest.spyOn(clipboardData, 'setData'));
+
+ describe('list handling', () => {
+ it('uses correct gfm for unordered lists', done => {
+ const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'UL');
+
+ window.getSelection = jest.fn(() => selection);
+ simulateCopy();
+
+ setImmediate(() => {
+ const expectedGFM = '* List Item1\n* List Item2';
+
+ expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
+ done();
+ });
+ });
+
+ it('uses correct gfm for ordered lists', done => {
+ const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'OL');
+
+ window.getSelection = jest.fn(() => selection);
+ simulateCopy();
+
+ setImmediate(() => {
+ const expectedGFM = '1. List Item1\n1. List Item2';
+
+ expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js b/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js
new file mode 100644
index 00000000000..aaee9c30cac
--- /dev/null
+++ b/spec/frontend/behaviors/gl_emoji/unicode_support_map_spec.js
@@ -0,0 +1,52 @@
+import getUnicodeSupportMap from '~/emoji/support/unicode_support_map';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+
+describe('Unicode Support Map', () => {
+ useLocalStorageSpy();
+ describe('getUnicodeSupportMap', () => {
+ const stringSupportMap = 'stringSupportMap';
+
+ beforeEach(() => {
+ jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockImplementation(() => {});
+ jest.spyOn(JSON, 'parse').mockImplementation(() => {});
+ jest.spyOn(JSON, 'stringify').mockReturnValue(stringSupportMap);
+ });
+
+ describe('if isLocalStorageAvailable is `true`', () => {
+ beforeEach(() => {
+ jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
+
+ getUnicodeSupportMap();
+ });
+
+ it('should call .getItem and .setItem', () => {
+ const getArgs = window.localStorage.getItem.mock.calls;
+ const setArgs = window.localStorage.setItem.mock.calls;
+
+ expect(getArgs[0][0]).toBe('gl-emoji-version');
+ expect(getArgs[1][0]).toBe('gl-emoji-user-agent');
+
+ expect(setArgs[0][0]).toBe('gl-emoji-version');
+ expect(setArgs[0][1]).toBe('0.2.0');
+ expect(setArgs[1][0]).toBe('gl-emoji-user-agent');
+ expect(setArgs[1][1]).toBe(navigator.userAgent);
+ expect(setArgs[2][0]).toBe('gl-emoji-unicode-support-map');
+ expect(setArgs[2][1]).toBe(stringSupportMap);
+ });
+ });
+
+ describe('if isLocalStorageAvailable is `false`', () => {
+ beforeEach(() => {
+ jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(false);
+
+ getUnicodeSupportMap();
+ });
+
+ it('should not call .getItem or .setItem', () => {
+ expect(window.localStorage.getItem.mock.calls.length).toBe(1);
+ expect(window.localStorage.setItem).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
new file mode 100644
index 00000000000..3305ddc412d
--- /dev/null
+++ b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
@@ -0,0 +1,55 @@
+import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
+
+describe('highlightCurrentUser', () => {
+ let rootElement;
+ let elements;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div id="dummy-root-element">
+ <div data-user="1">@first</div>
+ <div data-user="2">@second</div>
+ </div>
+ `);
+ rootElement = document.getElementById('dummy-root-element');
+ elements = rootElement.querySelectorAll('[data-user]');
+ });
+
+ describe('without current user', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.current_user_id = null;
+ });
+
+ afterEach(() => {
+ delete window.gon.current_user_id;
+ });
+
+ it('does not highlight the user', () => {
+ const initialHtml = rootElement.outerHTML;
+
+ highlightCurrentUser(elements);
+
+ expect(rootElement.outerHTML).toBe(initialHtml);
+ });
+ });
+
+ describe('with current user', () => {
+ beforeEach(() => {
+ window.gon = window.gon || {};
+ window.gon.current_user_id = 2;
+ });
+
+ afterEach(() => {
+ delete window.gon.current_user_id;
+ });
+
+ it('highlights current user', () => {
+ highlightCurrentUser(elements);
+
+ expect(elements.length).toBe(2);
+ expect(elements[0]).not.toHaveClass('current-user');
+ expect(elements[1]).toHaveClass('current-user');
+ });
+ });
+});
diff --git a/spec/frontend/behaviors/requires_input_spec.js b/spec/frontend/behaviors/requires_input_spec.js
new file mode 100644
index 00000000000..617fe49b059
--- /dev/null
+++ b/spec/frontend/behaviors/requires_input_spec.js
@@ -0,0 +1,62 @@
+import $ from 'jquery';
+import '~/behaviors/requires_input';
+
+describe('requiresInput', () => {
+ let submitButton;
+ preloadFixtures('branches/new_branch.html');
+
+ beforeEach(() => {
+ loadFixtures('branches/new_branch.html');
+ submitButton = $('button[type="submit"]');
+ });
+
+ it('disables submit when any field is required', () => {
+ $('.js-requires-input').requiresInput();
+
+ expect(submitButton).toBeDisabled();
+ });
+
+ it('enables submit when no field is required', () => {
+ $('*[required=required]').prop('required', false);
+ $('.js-requires-input').requiresInput();
+
+ expect(submitButton).not.toBeDisabled();
+ });
+
+ it('enables submit when all required fields are pre-filled', () => {
+ $('*[required=required]').remove();
+ $('.js-requires-input').requiresInput();
+
+ expect($('.submit')).not.toBeDisabled();
+ });
+
+ it('enables submit when all required fields receive input', () => {
+ $('.js-requires-input').requiresInput();
+ $('#required1')
+ .val('input1')
+ .change();
+
+ expect(submitButton).toBeDisabled();
+
+ $('#optional1')
+ .val('input1')
+ .change();
+
+ expect(submitButton).toBeDisabled();
+
+ $('#required2')
+ .val('input2')
+ .change();
+ $('#required3')
+ .val('input3')
+ .change();
+ $('#required4')
+ .val('input4')
+ .change();
+ $('#required5')
+ .val('1')
+ .change();
+
+ expect($('.submit')).not.toBeDisabled();
+ });
+});
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
new file mode 100644
index 00000000000..6391a544985
--- /dev/null
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -0,0 +1,322 @@
+import $ from 'jquery';
+import 'mousetrap';
+import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
+import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
+import { getSelectedFragment } from '~/lib/utils/common_utils';
+
+const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ ...jest.requireActual('~/lib/utils/common_utils'),
+ getSelectedFragment: jest.fn().mockName('getSelectedFragment'),
+}));
+
+describe('ShortcutsIssuable', () => {
+ const fixtureName = 'snippets/show.html';
+
+ preloadFixtures(fixtureName);
+
+ beforeAll(done => {
+ initCopyAsGFM();
+
+ // Fake call to nodeToGfm so the import of lazy bundle happened
+ CopyAsGFM.nodeToGFM(document.createElement('div'))
+ .then(() => {
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ $('body').append(
+ `<div class="js-main-target-form">
+ <textarea class="js-vue-comment-form"></textarea>
+ </div>`,
+ );
+ document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
+
+ window.shortcut = new ShortcutsIssuable(true);
+ });
+
+ afterEach(() => {
+ $(FORM_SELECTOR).remove();
+
+ delete window.shortcut;
+ });
+
+ describe('replyWithSelectedText', () => {
+ // Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
+ const stubSelection = (html, invalidNode) => {
+ getSelectedFragment.mockImplementation(() => {
+ const documentFragment = document.createDocumentFragment();
+ const node = document.createElement('div');
+
+ node.innerHTML = html;
+ if (!invalidNode) node.className = 'md';
+
+ documentFragment.appendChild(node);
+ return documentFragment;
+ });
+ };
+
+ describe('with empty selection', () => {
+ it('does not return an error', () => {
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect($(FORM_SELECTOR).val()).toBe('');
+ });
+
+ it('triggers `focus`', () => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ expect(spy).toHaveBeenCalled();
+ });
+ });
+
+ describe('with any selection', () => {
+ beforeEach(() => {
+ stubSelection('<p>Selected text.</p>');
+ });
+
+ it('leaves existing input intact', done => {
+ $(FORM_SELECTOR).val('This text was already here.');
+
+ expect($(FORM_SELECTOR).val()).toBe('This text was already here.');
+
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe(
+ 'This text was already here.\n\n> Selected text.\n\n',
+ );
+ done();
+ });
+ });
+
+ it('triggers `input`', done => {
+ let triggered = false;
+ $(FORM_SELECTOR).on('input', () => {
+ triggered = true;
+ });
+
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(triggered).toBe(true);
+ done();
+ });
+ });
+
+ it('triggers `focus`', done => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+
+ describe('with a one-line selection', () => {
+ it('quotes the selection', done => {
+ stubSelection('<p>This text has been selected.</p>');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
+ done();
+ });
+ });
+ });
+
+ describe('with a multi-line selection', () => {
+ it('quotes the selected lines as a group', done => {
+ stubSelection(
+ '<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>',
+ );
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe(
+ '> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
+ );
+ done();
+ });
+ });
+ });
+
+ describe('with an invalid selection', () => {
+ beforeEach(() => {
+ stubSelection('<p>Selected text.</p>', true);
+ });
+
+ it('does not add anything to the input', done => {
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('');
+ done();
+ });
+ });
+
+ it('triggers `focus`', done => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+
+ describe('with a semi-valid selection', () => {
+ beforeEach(() => {
+ stubSelection('<div class="md">Selected text.</div><p>Invalid selected text.</p>', true);
+ });
+
+ it('only adds the valid part to the input', done => {
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n');
+ done();
+ });
+ });
+
+ it('triggers `focus`', done => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('triggers `input`', done => {
+ let triggered = false;
+ $(FORM_SELECTOR).on('input', () => {
+ triggered = true;
+ });
+
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(triggered).toBe(true);
+ done();
+ });
+ });
+ });
+
+ describe('with a selection in a valid block', () => {
+ beforeEach(() => {
+ getSelectedFragment.mockImplementation(() => {
+ const documentFragment = document.createDocumentFragment();
+ const node = document.createElement('div');
+ const originalNode = document.createElement('body');
+ originalNode.innerHTML = `<div class="issue">
+ <div class="otherElem">Text...</div>
+ <div class="md"><p><em>Selected text.</em></p></div>
+ </div>`;
+ documentFragment.originalNodes = [originalNode.querySelector('em')];
+
+ node.innerHTML = '<em>Selected text.</em>';
+
+ documentFragment.appendChild(node);
+
+ return documentFragment;
+ });
+ });
+
+ it('adds the quoted selection to the input', done => {
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
+ done();
+ });
+ });
+
+ it('triggers `focus`', done => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('triggers `input`', done => {
+ let triggered = false;
+ $(FORM_SELECTOR).on('input', () => {
+ triggered = true;
+ });
+
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(triggered).toBe(true);
+ done();
+ });
+ });
+ });
+
+ describe('with a selection in an invalid block', () => {
+ beforeEach(() => {
+ getSelectedFragment.mockImplementation(() => {
+ const documentFragment = document.createDocumentFragment();
+ const node = document.createElement('div');
+ const originalNode = document.createElement('body');
+ originalNode.innerHTML = `<div class="issue">
+ <div class="otherElem"><div><b>Selected text.</b></div></div>
+ <div class="md"><p><em>Valid text</em></p></div>
+ </div>`;
+ documentFragment.originalNodes = [originalNode.querySelector('b')];
+
+ node.innerHTML = '<b>Selected text.</b>';
+
+ documentFragment.appendChild(node);
+
+ return documentFragment;
+ });
+ });
+
+ it('does not add anything to the input', done => {
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('');
+ done();
+ });
+ });
+
+ it('triggers `focus`', done => {
+ const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect(spy).toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+
+ describe('with a valid selection with no text content', () => {
+ it('returns the proper markdown', done => {
+ stubSelection('<img src="https://gitlab.com/logo.png" alt="logo" />');
+ ShortcutsIssuable.replyWithSelectedText(true);
+
+ setImmediate(() => {
+ expect($(FORM_SELECTOR).val()).toBe('> ![logo](https://gitlab.com/logo.png)\n\n');
+
+ done();
+ });
+ });
+ });
+ });
+});