diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-12 03:08:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-12 03:08:44 +0300 |
commit | 0f5dcf55e543e2cb30697d4c8ea8ce509cf25375 (patch) | |
tree | 213f71f90ed2f6d356a90b0d09e8dbf5d14e816e /spec | |
parent | a66475b6beb46d77b9ff3fe30453be2d52779048 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
33 files changed, 621 insertions, 43 deletions
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index 9cd335ffb8c..f5c5a73c042 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Admin Groups' do let!(:current_user) { create(:admin) } before do + stub_feature_flags(vue_group_members_list: false) + sign_in(current_user) stub_application_setting(default_group_visibility: internal) end diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb index 643c8407578..d667690af29 100644 --- a/spec/features/groups/members/filter_members_spec.rb +++ b/spec/features/groups/members/filter_members_spec.rb @@ -10,6 +10,8 @@ RSpec.describe 'Groups > Members > Filter members' do let(:nested_group) { create(:group, parent: group) } before do + stub_feature_flags(vue_group_members_list: false) + group.add_owner(user) group.add_maintainer(user_with_2fa) nested_group.add_maintainer(nested_group_user) diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index fecc90f20c7..9eb5cc15c5e 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Groups > Members > Leave group' do let(:group) { create(:group) } before do + stub_feature_flags(vue_group_members_list: false) + gitlab_sign_in(user) end diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb index 415c6927320..bcec2b50a24 100644 --- a/spec/features/groups/members/list_members_spec.rb +++ b/spec/features/groups/members/list_members_spec.rb @@ -12,6 +12,8 @@ RSpec.describe 'Groups > Members > List members' do let(:nested_group) { create(:group, parent: group) } before do + stub_feature_flags(vue_group_members_list: false) + sign_in(user1) end diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index faf455e4ed9..e3bbbd4d73b 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Groups > Members > Manage groups', :js do let(:shared_group) { create(:group) } before do + stub_feature_flags(vue_group_members_list: false) + shared_group.add_owner(user) sign_in(user) end diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb index 0267bea2f53..aedb7c170f8 100644 --- a/spec/features/groups/members/manage_members_spec.rb +++ b/spec/features/groups/members/manage_members_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Groups > Members > Manage members' do let(:group) { create(:group) } before do + stub_feature_flags(vue_group_members_list: false) + sign_in(user1) end diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb index f80925186ed..d94cc85f411 100644 --- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js let(:group) { create(:group) } before do + stub_feature_flags(vue_group_members_list: false) + group.add_owner(user1) sign_in(user1) end diff --git a/spec/features/groups/members/master_manages_access_requests_spec.rb b/spec/features/groups/members/master_manages_access_requests_spec.rb index 71c9b280ebe..44fd7380b79 100644 --- a/spec/features/groups/members/master_manages_access_requests_spec.rb +++ b/spec/features/groups/members/master_manages_access_requests_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe 'Groups > Members > Maintainer manages access requests' do + before do + stub_feature_flags(vue_group_members_list: false) + end + it_behaves_like 'Maintainer manages access requests' do let(:has_tabs) { true } let(:entity) { create(:group, :public) } diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index ad4f5c0b579..a95b59cece1 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -14,6 +14,8 @@ RSpec.describe 'Search group member' do end before do + stub_feature_flags(vue_group_members_list: false) + sign_in(user) visit group_group_members_path(guest_group) end diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index cfc0e421aeb..d940550b18a 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -8,6 +8,8 @@ RSpec.describe 'Groups > Members > Sort members' do let(:group) { create(:group) } before do + stub_feature_flags(vue_group_members_list: false) + create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago) create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago) diff --git a/spec/features/markdown/keyboard_shortcuts_spec.rb b/spec/features/markdown/keyboard_shortcuts_spec.rb new file mode 100644 index 00000000000..ff028a0281f --- /dev/null +++ b/spec/features/markdown/keyboard_shortcuts_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Markdown keyboard shortcuts', :js do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + before do + project.add_developer(user) + + gitlab_sign_in(user) + + visit path_to_visit + + wait_for_requests + end + + shared_examples 'keyboard shortcuts for modifier key' do + it 'bolds text when <modifier>+B is pressed' do + type_and_select('bold') + + markdown_field.send_keys([modifier_key, 'b']) + + expect(markdown_field.value).to eq('**bold**') + end + + it 'italicizes text when <modifier>+I is pressed' do + type_and_select('italic') + + markdown_field.send_keys([modifier_key, 'i']) + + expect(markdown_field.value).to eq('_italic_') + end + + it 'links text when <modifier>+K is pressed' do + type_and_select('link') + + markdown_field.send_keys([modifier_key, 'k']) + + expect(markdown_field.value).to eq('[link](url)') + + # Type some more text to ensure the cursor + # and selection are set correctly + markdown_field.send_keys('https://example.com') + + expect(markdown_field.value).to eq('[link](https://example.com)') + end + + it 'does not affect non-markdown fields on the same page' do + non_markdown_field.send_keys('some text') + + non_markdown_field.send_keys([modifier_key, 'b']) + + expect(focused_element).to eq(non_markdown_field.native) + expect(markdown_field.value).to eq('') + end + end + + shared_examples 'keyboard shortcuts for implementation' do + context 'Ctrl key' do + let(:modifier_key) { :control } + + it_behaves_like 'keyboard shortcuts for modifier key' + end + + context '⌘ key' do + let(:modifier_key) { :command } + + it_behaves_like 'keyboard shortcuts for modifier key' + end + end + + context 'Vue.js markdown editor' do + let(:path_to_visit) { new_project_release_path(project) } + let(:markdown_field) { find_field('Release notes') } + let(:non_markdown_field) { find_field('Release title') } + + it_behaves_like 'keyboard shortcuts for implementation' + end + + context 'Haml markdown editor' do + let(:path_to_visit) { new_project_issue_path(project) } + let(:markdown_field) { find_field('Description') } + let(:non_markdown_field) { find_field('Title') } + + it_behaves_like 'keyboard shortcuts for implementation' + end + + def type_and_select(text) + markdown_field.send_keys(text) + + text.length.times do + markdown_field.send_keys([:shift, :arrow_left]) + end + end + + def focused_element + page.driver.browser.switch_to.active_element + end +end diff --git a/spec/fixtures/api/schemas/entities/group_group_link.json b/spec/fixtures/api/schemas/entities/group_group_link.json new file mode 100644 index 00000000000..4c9aae140d2 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/group_group_link.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "required": ["id", "created_at", "expires_at", "access_level"], + "properties": { + "id": { "type": "integer" }, + "created_at": { "type": "date-time" }, + "expires_at": { "type": ["date-time", "null"] }, + "access_level": { + "type": "object", + "required": ["integer_value", "string_value"], + "properties": { + "integer_value": { "type": "integer" }, + "string_value": { "type": "string" } + } + }, + "shared_with_group": { + "type": "object", + "required": ["id", "name", "full_name", "full_path", "avatar_url", "web_url"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "full_name": { "type": "string" }, + "full_path": { "type": "string" }, + "avatar_url": { "type": ["string", "null"] }, + "web_url": { "type": "string" } + } + } + } +} diff --git a/spec/fixtures/api/schemas/group_group_links.json b/spec/fixtures/api/schemas/group_group_links.json new file mode 100644 index 00000000000..f8b4e7f035b --- /dev/null +++ b/spec/fixtures/api/schemas/group_group_links.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "entities/group_group_link.json" + } +} diff --git a/spec/fixtures/api/schemas/group_member.json b/spec/fixtures/api/schemas/group_member.json new file mode 100644 index 00000000000..035c862d229 --- /dev/null +++ b/spec/fixtures/api/schemas/group_member.json @@ -0,0 +1,78 @@ +{ + "type": "object", + "required": [ + "id", + "created_at", + "expires_at", + "access_level", + "requested_at", + "source", + "can_update", + "can_remove", + "can_override" + ], + "properties": { + "id": { "type": "integer" }, + "created_at": { "type": "date-time" }, + "expires_at": { "type": ["date-time", "null"] }, + "requested_at": { "type": ["date-time", "null"] }, + "can_update": { "type": "boolean" }, + "can_remove": { "type": "boolean" }, + "can_override": { "type": "boolean" }, + "access_level": { + "type": "object", + "required": ["integer_value", "string_value"], + "properties": { + "integer_value": { "type": "integer" }, + "string_value": { "type": "string" } + } + }, + "source": { + "type": "object", + "required": ["id", "name", "web_url"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "web_url": { "type": "string" } + } + }, + "created_by": { + "type": "object", + "required": ["name", "web_url"], + "properties": { + "name": { "type": "string" }, + "web_url": { "type": "string" } + } + }, + "user": { + "type": "object", + "required": [ + "id", + "name", + "username", + "avatar_url", + "web_url", + "blocked", + "two_factor_enabled" + ], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "username": { "type": "string" }, + "avatar_url": { "type": ["string", "null"] }, + "web_url": { "type": "string" }, + "blocked": { "type": "boolean" }, + "two_factor_enabled": { "type": "boolean" } + } + }, + "invite": { + "type": "object", + "required": ["email", "avatar_url", "can_resend"], + "properties": { + "email": { "type": "string" }, + "avatar_url": { "type": "string" }, + "can_resend": { "type": "boolean" } + } + } + } +} diff --git a/spec/fixtures/api/schemas/group_members.json b/spec/fixtures/api/schemas/group_members.json new file mode 100644 index 00000000000..6268c7ef4d8 --- /dev/null +++ b/spec/fixtures/api/schemas/group_members.json @@ -0,0 +1,6 @@ +{ + "type": "array", + "items": { + "$ref": "group_member.json" + } +} diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js index d78d3dc7edd..d9f7146d258 100644 --- a/spec/frontend/design_management/pages/design/index_spec.js +++ b/spec/frontend/design_management/pages/design/index_spec.js @@ -22,10 +22,6 @@ import mockResponseNoDesigns from '../../mock_data/no_designs'; import mockAllVersions from '../../mock_data/all_versions'; jest.mock('~/flash'); -jest.mock('mousetrap', () => ({ - bind: jest.fn(), - unbind: jest.fn(), -})); const focusInput = jest.fn(); diff --git a/spec/frontend/design_management/router_spec.js b/spec/frontend/design_management/router_spec.js index 2b8c7ee959b..d4cb9f75a77 100644 --- a/spec/frontend/design_management/router_spec.js +++ b/spec/frontend/design_management/router_spec.js @@ -35,11 +35,6 @@ function factory(routeArg) { }); } -jest.mock('mousetrap', () => ({ - bind: jest.fn(), - unbind: jest.fn(), -})); - describe('Design management router', () => { afterEach(() => { window.location.hash = ''; diff --git a/spec/frontend/groups/members/index_spec.js b/spec/frontend/groups/members/index_spec.js new file mode 100644 index 00000000000..8673f257a1f --- /dev/null +++ b/spec/frontend/groups/members/index_spec.js @@ -0,0 +1,58 @@ +import { createWrapper } from '@vue/test-utils'; +import initGroupMembersApp from '~/groups/members'; +import GroupMembersApp from '~/groups/members/components/app.vue'; +import { membersJsonString, membersParsed } from './mock_data'; + +describe('initGroupMembersApp', () => { + let el; + let wrapper; + + const setup = () => { + const vm = initGroupMembersApp(el); + wrapper = createWrapper(vm); + }; + + const getGroupMembersApp = () => wrapper.find(GroupMembersApp); + + beforeEach(() => { + el = document.createElement('div'); + el.setAttribute('data-members', membersJsonString); + el.setAttribute('data-current-user-id', '123'); + el.setAttribute('data-group-id', '234'); + + document.body.appendChild(el); + }); + + afterEach(() => { + document.body.innerHTML = ''; + el = null; + + wrapper.destroy(); + wrapper = null; + }); + + it('parses and passes `currentUserId` prop to `GroupMembersApp`', () => { + setup(); + + expect(getGroupMembersApp().props('currentUserId')).toBe(123); + }); + + it('does not pass `currentUserId` prop if not provided by the data attribute (user is not logged in)', () => { + el.removeAttribute('data-current-user-id'); + setup(); + + expect(getGroupMembersApp().props('currentUserId')).toBeNull(); + }); + + it('parses and passes `groupId` prop to `GroupMembersApp`', () => { + setup(); + + expect(getGroupMembersApp().props('groupId')).toBe(234); + }); + + it('parses and passes `members` prop to `GroupMembersApp`', () => { + setup(); + + expect(getGroupMembersApp().props('members')).toEqual(membersParsed); + }); +}); diff --git a/spec/frontend/groups/members/mock_data.js b/spec/frontend/groups/members/mock_data.js new file mode 100644 index 00000000000..b84c9c6d446 --- /dev/null +++ b/spec/frontend/groups/members/mock_data.js @@ -0,0 +1,33 @@ +export const membersJsonString = + '[{"requested_at":null,"can_update":true,"can_remove":true,"can_override":false,"access_level":{"integer_value":50,"string_value":"Owner"},"source":{"id":323,"name":"My group / my subgroup","web_url":"http://127.0.0.1:3000/groups/my-group/my-subgroup"},"user":{"id":1,"name":"Administrator","username":"root","web_url":"http://127.0.0.1:3000/root","avatar_url":"https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80\u0026d=identicon","blocked":false,"two_factor_enabled":false},"id":524,"created_at":"2020-08-21T21:33:27.631Z","expires_at":null,"using_license":false,"group_sso":false,"group_managed_account":false}]'; + +export const membersParsed = [ + { + requestedAt: null, + canUpdate: true, + canRemove: true, + canOverride: false, + accessLevel: { integerValue: 50, stringValue: 'Owner' }, + source: { + id: 323, + name: 'My group / my subgroup', + webUrl: 'http://127.0.0.1:3000/groups/my-group/my-subgroup', + }, + user: { + id: 1, + name: 'Administrator', + username: 'root', + webUrl: 'http://127.0.0.1:3000/root', + avatarUrl: + 'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon', + blocked: false, + twoFactorEnabled: false, + }, + id: 524, + createdAt: '2020-08-21T21:33:27.631Z', + expiresAt: null, + usingLicense: false, + groupSso: false, + groupManagedAccount: false, + }, +]; diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js index 3d16074154c..21258e09356 100644 --- a/spec/frontend/shortcuts_spec.js +++ b/spec/frontend/shortcuts_spec.js @@ -1,6 +1,18 @@ import $ from 'jquery'; +import { flatten } from 'lodash'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; +const mockMousetrap = { + bind: jest.fn(), + unbind: jest.fn(), +}; + +jest.mock('mousetrap', () => { + return jest.fn().mockImplementation(() => mockMousetrap); +}); + +jest.mock('mousetrap/plugins/pause/mousetrap-pause', () => {}); + describe('Shortcuts', () => { const fixtureName = 'snippets/show.html'; const createEvent = (type, target) => @@ -10,16 +22,16 @@ describe('Shortcuts', () => { preloadFixtures(fixtureName); - describe('toggleMarkdownPreview', () => { - beforeEach(() => { - loadFixtures(fixtureName); + beforeEach(() => { + loadFixtures(fixtureName); - jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus'); - jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus'); + jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus'); + jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus'); - new Shortcuts(); // eslint-disable-line no-new - }); + new Shortcuts(); // eslint-disable-line no-new + }); + describe('toggleMarkdownPreview', () => { it('focuses preview button in form', () => { Shortcuts.toggleMarkdownPreview( createEvent('KeyboardEvent', document.querySelector('.js-new-note-form .js-note-text')), @@ -43,4 +55,63 @@ describe('Shortcuts', () => { expect(document.querySelector('.edit-note .js-md-preview-button').focus).toHaveBeenCalled(); }); }); + + describe('markdown shortcuts', () => { + let shortcuts; + + beforeEach(() => { + // Get all shortcuts specified with md-shortcuts attributes in the fixture. + // `shortcuts` will look something like this: + // [ + // [ 'command+b', 'ctrl+b' ], + // [ 'command+i', 'ctrl+i' ], + // [ 'command+k', 'ctrl+k' ] + // ] + shortcuts = $('.edit-note .js-md') + .map(function getShortcutsFromToolbarBtn() { + const mdShortcuts = $(this).data('md-shortcuts'); + + // jQuery.map() automatically unwraps arrays, so we + // have to double wrap the array to counteract this: + // https://stackoverflow.com/a/4875669/1063392 + return mdShortcuts ? [mdShortcuts] : undefined; + }) + .get(); + }); + + describe('initMarkdownEditorShortcuts', () => { + beforeEach(() => { + Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea')); + }); + + it('attaches a Mousetrap handler for every markdown shortcut specified with md-shortcuts', () => { + const expectedCalls = shortcuts.map(s => [s, expect.any(Function)]); + + expect(mockMousetrap.bind.mock.calls).toEqual(expectedCalls); + }); + + it('attaches a stopCallback that allows each markdown shortcut specified with md-shortcuts', () => { + flatten(shortcuts).forEach(s => { + expect(mockMousetrap.stopCallback(null, null, s)).toBe(false); + }); + }); + }); + + describe('removeMarkdownEditorShortcuts', () => { + it('does nothing if initMarkdownEditorShortcuts was not previous called', () => { + Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea')); + + expect(mockMousetrap.unbind.mock.calls).toEqual([]); + }); + + it('removes Mousetrap handlers for every markdown shortcut specified with md-shortcuts', () => { + Shortcuts.initMarkdownEditorShortcuts($('.edit-note textarea')); + Shortcuts.removeMarkdownEditorShortcuts($('.edit-note textarea')); + + const expectedCalls = shortcuts.map(s => [s]); + + expect(mockMousetrap.unbind.mock.calls).toEqual(expectedCalls); + }); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 551d781d296..82bc9b9fe08 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -22,6 +22,12 @@ describe('Markdown field header component', () => { .at(0); beforeEach(() => { + window.gl = { + client: { + isMac: true, + }, + }; + createWrapper(); }); @@ -30,24 +36,40 @@ describe('Markdown field header component', () => { wrapper = null; }); - it('renders markdown header buttons', () => { - const buttons = [ - 'Add bold text', - 'Add italic text', - 'Insert a quote', - 'Insert suggestion', - 'Insert code', - 'Add a link', - 'Add a bullet list', - 'Add a numbered list', - 'Add a task list', - 'Add a table', - 'Go full screen', - ]; - const elements = findToolbarButtons(); - - elements.wrappers.forEach((buttonEl, index) => { - expect(buttonEl.props('buttonTitle')).toBe(buttons[index]); + describe('markdown header buttons', () => { + it('renders the buttons with the correct title', () => { + const buttons = [ + 'Add bold text (⌘B)', + 'Add italic text (⌘I)', + 'Insert a quote', + 'Insert suggestion', + 'Insert code', + 'Add a link (⌘K)', + 'Add a bullet list', + 'Add a numbered list', + 'Add a task list', + 'Add a table', + 'Go full screen', + ]; + const elements = findToolbarButtons(); + + elements.wrappers.forEach((buttonEl, index) => { + expect(buttonEl.props('buttonTitle')).toBe(buttons[index]); + }); + }); + + describe('when the user is on a non-Mac', () => { + beforeEach(() => { + delete window.gl.client.isMac; + + createWrapper(); + }); + + it('renders keyboard shortcuts with Ctrl+ instead of ⌘', () => { + const boldButton = findToolbarButtonByProp('icon', 'bold'); + + expect(boldButton.props('buttonTitle')).toBe('Add bold text (Ctrl+B)'); + }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js new file mode 100644 index 00000000000..8a7946fd7b1 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue'; + +describe('toolbar_button', () => { + let wrapper; + + const defaultProps = { + buttonTitle: 'test button', + icon: 'rocket', + tag: 'test tag', + }; + + const createComponent = propUpdates => { + wrapper = shallowMount(ToolbarButton, { + propsData: { + ...defaultProps, + ...propUpdates, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const getButtonShortcutsAttr = () => { + return wrapper.find('button').attributes('data-md-shortcuts'); + }; + + describe('keyboard shortcuts', () => { + it.each` + shortcutsProp | mdShortcutsAttr + ${undefined} | ${JSON.stringify([])} + ${[]} | ${JSON.stringify([])} + ${'command+b'} | ${JSON.stringify(['command+b'])} + ${['command+b', 'ctrl+b']} | ${JSON.stringify(['command+b', 'ctrl+b'])} + `( + 'adds the attribute data-md-shortcuts="$mdShortcutsAttr" to the button when the shortcuts prop is $shortcutsProp', + ({ shortcutsProp, mdShortcutsAttr }) => { + createComponent({ shortcuts: shortcutsProp }); + + expect(getButtonShortcutsAttr()).toBe(mdShortcutsAttr); + }, + ); + }); +}); diff --git a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb index 2fe3e86ec14..ae3097c1d9e 100644 --- a/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb @@ -29,4 +29,11 @@ RSpec.describe Resolvers::MergeRequestPipelinesResolver do it 'resolves only MRs for the passed merge request' do expect(resolve_pipelines).to contain_exactly(pipeline) end + + describe 'with archived project' do + let(:archived_project) { create(:project, :archived) } + let(:merge_request) { create(:merge_request, source_project: archived_project) } + + it { expect(resolve_pipelines).not_to contain_exactly(pipeline) } + end end diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index 1279f01f104..7272a952f77 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -27,6 +27,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do upvotes downvotes head_pipeline pipelines task_completion_status milestone assignees participants subscribed labels discussion_locked time_estimate total_time_spent reference author merged_at commit_count current_user_todos + conflicts auto_merge_enabled ] if Gitlab.ee? diff --git a/spec/graphql/types/permission_types/merge_request_spec.rb b/spec/graphql/types/permission_types/merge_request_spec.rb index 73a178540a6..2849dead9a8 100644 --- a/spec/graphql/types/permission_types/merge_request_spec.rb +++ b/spec/graphql/types/permission_types/merge_request_spec.rb @@ -7,7 +7,8 @@ RSpec.describe Types::PermissionTypes::MergeRequest do expected_permissions = [ :read_merge_request, :admin_merge_request, :update_merge_request, :create_note, :push_to_source_branch, :remove_source_branch, - :cherry_pick_on_current_merge_request, :revert_on_current_merge_request + :cherry_pick_on_current_merge_request, :revert_on_current_merge_request, + :can_merge ] expect(described_class).to have_graphql_fields(expected_permissions) diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb index aa4c5dd7c39..68de8317b82 100644 --- a/spec/haml_lint/linter/documentation_links_spec.rb +++ b/spec/haml_lint/linter/documentation_links_spec.rb @@ -20,13 +20,6 @@ RSpec.describe HamlLint::Linter::DocumentationLinks do it { is_expected.not_to report_lint } end - # TODO: Remove me after https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39715 is merged - context 'when link_to points to the existing file with partially matching anchor' do - let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'overview-premium'), target: '_blank'" } - - it { is_expected.not_to report_lint } - end - context 'when link_to points to the existing file path without .md extension' do let(:haml) { "= link_to 'Description', help_page_path('README')" } diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb index 90792331d9b..a25bf1c4157 100644 --- a/spec/helpers/groups/group_members_helper_spec.rb +++ b/spec/helpers/groups/group_members_helper_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe Groups::GroupMembersHelper do + include MembersPresentation + describe '.group_member_select_options' do let(:group) { create(:group) } @@ -14,4 +16,50 @@ RSpec.describe Groups::GroupMembersHelper do expect(helper.group_member_select_options).to include(multiple: true, scope: :all, email_user: true) end end + + describe '#linked_groups_data_json' do + include_context 'group_group_link' + + it 'matches json schema' do + json = helper.linked_groups_data_json(shared_group.shared_with_group_links) + + expect(json).to match_schema('group_group_links') + end + end + + describe '#members_data_json' do + let(:current_user) { create(:user) } + let(:group) { create(:group) } + + before do + allow(helper).to receive(:can?).with(current_user, :owner_access, group).and_return(true) + allow(helper).to receive(:current_user).and_return(current_user) + end + + shared_examples 'group_members.json' do + it 'matches json schema' do + json = helper.members_data_json(group, present_members([group_member])) + + expect(json).to match_schema('group_members') + end + end + + context 'for a group member' do + let(:group_member) { create(:group_member, group: group, created_by: current_user) } + + it_behaves_like 'group_members.json' + end + + context 'for an invited group member' do + let(:group_member) { create(:group_member, :invited, group: group, created_by: current_user) } + + it_behaves_like 'group_members.json' + end + + context 'for an access request' do + let(:group_member) { create(:group_member, :access_request, group: group, created_by: current_user) } + + it_behaves_like 'group_members.json' + end + end end diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb index 0ac594ca830..93d91f7ed90 100644 --- a/spec/lib/gitlab/utils/markdown_spec.rb +++ b/spec/lib/gitlab/utils/markdown_spec.rb @@ -66,6 +66,22 @@ RSpec.describe Gitlab::Utils::Markdown do is_expected.to eq 'my-header' end end + + context 'with "*" around a product suffix' do + let(:string) { 'My Header **(STARTER)**' } + + it 'ignores a product suffix' do + is_expected.to eq 'my-header' + end + end + + context 'with "*" around a product suffix and only modifier' do + let(:string) { 'My Header **(STARTER ONLY)**' } + + it 'ignores a product suffix' do + is_expected.to eq 'my-header' + end + end end context 'when string is empty' do diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index c39358a2db1..fae52fe814d 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -124,7 +124,8 @@ RSpec.describe 'getting merge request information nested in a project' do 'removeSourceBranch' => false, 'cherryPickOnCurrentMergeRequest' => false, 'revertOnCurrentMergeRequest' => false, - 'updateMergeRequest' => false + 'updateMergeRequest' => false, + 'canMerge' => false } post_graphql(query, current_user: current_user) diff --git a/spec/serializers/group_group_link_entity_spec.rb b/spec/serializers/group_group_link_entity_spec.rb new file mode 100644 index 00000000000..8384563e3e6 --- /dev/null +++ b/spec/serializers/group_group_link_entity_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GroupGroupLinkEntity do + include_context 'group_group_link' + + subject(:json) { described_class.new(group_group_link).to_json } + + it 'matches json schema' do + expect(json).to match_schema('entities/group_group_link') + end +end diff --git a/spec/serializers/group_group_link_serializer_spec.rb b/spec/serializers/group_group_link_serializer_spec.rb new file mode 100644 index 00000000000..0d977ea0a9a --- /dev/null +++ b/spec/serializers/group_group_link_serializer_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GroupGroupLinkSerializer do + include_context 'group_group_link' + + subject(:json) { described_class.new.represent(shared_group.shared_with_group_links).to_json } + + it 'matches json schema' do + expect(json).to match_schema('group_group_links') + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9e3e4cc6d64..11a83bd9501 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -204,6 +204,10 @@ RSpec.configure do |config| # unified diff lines works as expected stub_feature_flags(unified_diff_lines: false) + # Merge request widget GraphQL requests are disabled in the tests + # for now whilst we migrate as much as we can over the GraphQL + stub_feature_flags(merge_request_widget_graphql: false) + enable_rugged = example.metadata[:enable_rugged].present? # Disable Rugged features by default diff --git a/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb b/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb new file mode 100644 index 00000000000..fce78957eba --- /dev/null +++ b/spec/support/shared_contexts/serializers/group_group_link_shared_context.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +RSpec.shared_context 'group_group_link' do + let(:shared_with_group) { create(:group) } + let(:shared_group) { create(:group) } + + let!(:group_group_link) do + create( + :group_group_link, + { + shared_group: shared_group, + shared_with_group: shared_with_group, + expires_at: '2020-05-12' + } + ) + end +end |