diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
commit | 6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch) | |
tree | dc4d20fe6064752c0bd323187252c77e0a89144b /spec/frontend/pages | |
parent | 9868dae7fc0655bd7ce4a6887d4e6d487690eeed (diff) |
Add latest changes from gitlab-org/gitlab@15-4-stable-eev15.4.0-rc42
Diffstat (limited to 'spec/frontend/pages')
14 files changed, 489 insertions, 350 deletions
diff --git a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js index 3a52c243867..3c512cfd6ae 100644 --- a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js +++ b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js @@ -48,7 +48,7 @@ describe('UsageStatistics', () => { expectEnabledservicePingFeaturesCheckBox(); }); - it('is switched to disabled when Service Ping checkbox is unchecked ', () => { + it('is switched to disabled when Service Ping checkbox is unchecked', () => { servicePingCheckBox.click(); servicePingFeaturesCheckBox.click(); expectEnabledservicePingFeaturesCheckBox(); diff --git a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js index 7a8a249cb2a..b020caa3010 100644 --- a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js +++ b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js @@ -14,7 +14,7 @@ describe('BitbucketServerStatusTable', () => { const findReconfigureButton = () => wrapper - .findAll(GlButton) + .findAllComponents(GlButton) .filter((w) => w.props().variant === 'info') .at(0); @@ -36,7 +36,7 @@ describe('BitbucketServerStatusTable', () => { it('renders bitbucket status table component', () => { createComponent(); - expect(wrapper.find(BitbucketStatusTable).exists()).toBe(true); + expect(wrapper.findComponent(BitbucketStatusTable).exists()).toBe(true); }); it('renders Reconfigure button', async () => { diff --git a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js index a850b1655f7..1790a9c9bf5 100644 --- a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js +++ b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js @@ -84,7 +84,7 @@ describe('BulkImportsHistoryApp', () => { describe('general behavior', () => { it('renders loading state when loading', () => { createComponent(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); it('renders empty state when no data is available', async () => { @@ -92,8 +92,8 @@ describe('BulkImportsHistoryApp', () => { createComponent(); await axios.waitForAll(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(GlEmptyState).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true); }); it('renders table with data when history is available', async () => { @@ -101,7 +101,7 @@ describe('BulkImportsHistoryApp', () => { createComponent(); await axios.waitForAll(); - const table = wrapper.find(GlTable); + const table = wrapper.findComponent(GlTable); expect(table.exists()).toBe(true); // can't use .props() or .attributes() here expect(table.vm.$attrs.items).toHaveLength(DUMMY_RESPONSE.length); diff --git a/spec/frontend/pages/import/history/components/import_error_details_spec.js b/spec/frontend/pages/import/history/components/import_error_details_spec.js index 4ff3f0361cf..82a3e11186e 100644 --- a/spec/frontend/pages/import/history/components/import_error_details_spec.js +++ b/spec/frontend/pages/import/history/components/import_error_details_spec.js @@ -41,7 +41,7 @@ describe('ImportErrorDetails', () => { describe('general behavior', () => { it('renders loading state when loading', () => { createComponent(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); it('renders import_error if it is available', async () => { @@ -50,7 +50,7 @@ describe('ImportErrorDetails', () => { createComponent(); await axios.waitForAll(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); expect(wrapper.find('pre').text()).toBe(FAKE_IMPORT_ERROR); }); @@ -59,7 +59,7 @@ describe('ImportErrorDetails', () => { createComponent(); await axios.waitForAll(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); expect(wrapper.find('pre').text()).toBe('No additional information provided.'); }); }); diff --git a/spec/frontend/pages/import/history/components/import_history_app_spec.js b/spec/frontend/pages/import/history/components/import_history_app_spec.js index 0d821b114cf..5030adae2fa 100644 --- a/spec/frontend/pages/import/history/components/import_history_app_spec.js +++ b/spec/frontend/pages/import/history/components/import_history_app_spec.js @@ -79,7 +79,7 @@ describe('ImportHistoryApp', () => { describe('general behavior', () => { it('renders loading state when loading', () => { createComponent(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); it('renders empty state when no data is available', async () => { @@ -87,8 +87,8 @@ describe('ImportHistoryApp', () => { createComponent(); await axios.waitForAll(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(GlEmptyState).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true); }); it('renders table with data when history is available', async () => { @@ -96,7 +96,7 @@ describe('ImportHistoryApp', () => { createComponent(); await axios.waitForAll(); - const table = wrapper.find(GlTable); + const table = wrapper.findComponent(GlTable); expect(table.exists()).toBe(true); expect(table.props().items).toStrictEqual(DUMMY_RESPONSE); }); @@ -127,7 +127,7 @@ describe('ImportHistoryApp', () => { expect(mock.history.get.length).toBe(1); expect(mock.history.get[0].params).toStrictEqual(expect.objectContaining({ page: NEW_PAGE })); - expect(wrapper.find(GlTable).props().items).toStrictEqual(FAKE_NEXT_PAGE_REPLY); + expect(wrapper.findComponent(GlTable).props().items).toStrictEqual(FAKE_NEXT_PAGE_REPLY); }); }); diff --git a/spec/frontend/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js deleted file mode 100644 index fa6e7e51a60..00000000000 --- a/spec/frontend/pages/profiles/show/emoji_menu_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import $ from 'jquery'; -import { TEST_HOST } from 'helpers/test_constants'; -import axios from '~/lib/utils/axios_utils'; -import EmojiMenu from '~/pages/profiles/show/emoji_menu'; - -describe('EmojiMenu', () => { - const dummyEmojiTag = '<dummy></tag>'; - const dummyToggleButtonSelector = '.toggle-button-selector'; - const dummyMenuClass = 'dummy-menu-class'; - - let emojiMenu; - let dummySelectEmojiCallback; - let dummyEmojiList; - - beforeEach(() => { - dummySelectEmojiCallback = jest.fn().mockName('dummySelectEmojiCallback'); - dummyEmojiList = { - glEmojiTag() { - return dummyEmojiTag; - }, - normalizeEmojiName(emoji) { - return emoji; - }, - isEmojiNameValid() { - return true; - }, - getEmojiCategoryMap() { - return { dummyCategory: [] }; - }, - }; - - emojiMenu = new EmojiMenu( - dummyEmojiList, - dummyToggleButtonSelector, - dummyMenuClass, - dummySelectEmojiCallback, - ); - }); - - afterEach(() => { - emojiMenu.destroy(); - }); - - describe('addAward', () => { - const dummyAwardUrl = `${TEST_HOST}/award/url`; - const dummyEmoji = 'tropical_fish'; - const dummyVotesBlock = () => $('<div />'); - - it('calls selectEmojiCallback', async () => { - expect(dummySelectEmojiCallback).not.toHaveBeenCalled(); - - await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false); - expect(dummySelectEmojiCallback).toHaveBeenCalledWith(dummyEmoji, dummyEmojiTag); - }); - - it('does not make an axios request', async () => { - jest.spyOn(axios, 'request').mockReturnValue(); - - await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false); - expect(axios.request).not.toHaveBeenCalled(); - }); - }); - - describe('bindEvents', () => { - beforeEach(() => { - jest.spyOn(emojiMenu, 'registerEventListener').mockReturnValue(); - }); - - it('binds event listeners to custom toggle button', () => { - emojiMenu.bindEvents(); - - expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( - 'one', - expect.anything(), - 'mouseenter focus', - dummyToggleButtonSelector, - 'mouseenter focus', - expect.anything(), - ); - - expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( - 'on', - expect.anything(), - 'click', - dummyToggleButtonSelector, - expect.anything(), - ); - }); - - it('binds event listeners to custom menu class', () => { - emojiMenu.bindEvents(); - - expect(emojiMenu.registerEventListener).toHaveBeenCalledWith( - 'on', - expect.anything(), - 'click', - `.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`, - expect.anything(), - ); - }); - }); - - describe('createEmojiMenu', () => { - it('renders the menu with custom menu class', () => { - const menuElement = () => - document.body.querySelector(`.emoji-menu.${dummyMenuClass} .emoji-menu-content`); - - expect(menuElement()).toBe(null); - - emojiMenu.createEmojiMenu(); - - expect(menuElement()).not.toBe(null); - }); - }); -}); diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index 2a0fde45384..f221a90da61 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -4,11 +4,14 @@ import { mount, shallowMount } from '@vue/test-utils'; import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; import { kebabCase } from 'lodash'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; import createFlash from '~/flash'; -import httpStatus from '~/lib/utils/http_status'; import * as urlUtility from '~/lib/utils/url_utility'; import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql'; +import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue'; jest.mock('~/flash'); jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); @@ -16,6 +19,7 @@ jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); describe('ForkForm component', () => { let wrapper; let axiosMock; + let mockQueryResponse; const PROJECT_VISIBILITY_TYPE = { private: @@ -24,26 +28,11 @@ describe('ForkForm component', () => { public: 'Public The project can be accessed without any authentication.', }; - const GON_GITLAB_URL = 'https://gitlab.com'; const GON_API_VERSION = 'v7'; - const MOCK_NAMESPACES_RESPONSE = [ - { - name: 'one', - full_name: 'one-group/one', - id: 1, - }, - { - name: 'two', - full_name: 'two-group/two', - id: 2, - }, - ]; - const DEFAULT_PROVIDE = { newGroupPath: 'some/groups/path', visibilityHelpPath: 'some/visibility/help/path', - endpoint: '/some/project-full-path/-/forks/new.json', projectFullPath: '/some/project-full-path', projectId: '10', projectName: 'Project Name', @@ -53,12 +42,44 @@ describe('ForkForm component', () => { restrictedVisibilityLevels: [], }; - const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => { - axiosMock.onGet(DEFAULT_PROVIDE.endpoint).replyOnce(statusCode, data); - }; + Vue.use(VueApollo); const createComponentFactory = (mountFn) => (provide = {}, data = {}) => { + const queryResponse = { + project: { + id: 'gid://gitlab/Project/1', + forkTargets: { + nodes: [ + { + id: 'gid://gitlab/Group/21', + fullPath: 'flightjs', + name: 'Flight JS', + visibility: 'public', + }, + { + id: 'gid://gitlab/Namespace/4', + fullPath: 'root', + name: 'Administrator', + visibility: 'public', + }, + ], + }, + }, + }; + + mockQueryResponse = jest.fn().mockResolvedValue({ data: queryResponse }); + const requestHandlers = [[searchQuery, mockQueryResponse]]; + const apolloProvider = createMockApollo(requestHandlers); + + apolloProvider.clients.defaultClient.cache.writeQuery({ + query: searchQuery, + data: { + ...queryResponse, + }, + }); + wrapper = mountFn(ForkForm, { + apolloProvider, provide: { ...DEFAULT_PROVIDE, ...provide, @@ -83,7 +104,6 @@ describe('ForkForm component', () => { beforeEach(() => { axiosMock = new AxiosMockAdapter(axios); window.gon = { - gitlab_url: GON_GITLAB_URL, api_version: GON_API_VERSION, }; }); @@ -93,12 +113,11 @@ describe('ForkForm component', () => { axiosMock.restore(); }); - const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option'); const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]'); const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]'); const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]'); const findForkNameInput = () => wrapper.find('[data-testid="fork-name-input"]'); - const findForkUrlInput = () => wrapper.find('[data-testid="fork-url-input"]'); + const findForkUrlInput = () => wrapper.findComponent(ProjectNamespace); const findForkSlugInput = () => wrapper.find('[data-testid="fork-slug-input"]'); const findForkDescriptionTextarea = () => wrapper.find('[data-testid="fork-description-textarea"]'); @@ -106,7 +125,6 @@ describe('ForkForm component', () => { wrapper.find('[data-testid="fork-visibility-radio-group"]'); it('will go to projectFullPath when click cancel button', () => { - mockGetRequest(); createComponent(); const { projectFullPath } = DEFAULT_PROVIDE; @@ -115,8 +133,13 @@ describe('ForkForm component', () => { expect(cancelButton.attributes('href')).toBe(projectFullPath); }); + const selectedMockNamespace = { name: 'two', full_name: 'two-group/two', id: 2 }; + + const fillForm = () => { + findForkUrlInput().vm.$emit('select', selectedMockNamespace); + }; + it('has input with csrf token', () => { - mockGetRequest(); createComponent(); expect(wrapper.find('input[name="authenticity_token"]').attributes('value')).toBe( @@ -125,7 +148,6 @@ describe('ForkForm component', () => { }); it('pre-populate form from project props', () => { - mockGetRequest(); createComponent(); expect(findForkNameInput().attributes('value')).toBe(DEFAULT_PROVIDE.projectName); @@ -135,75 +157,19 @@ describe('ForkForm component', () => { ); }); - it('sets project URL prepend text with gon.gitlab_url', () => { - mockGetRequest(); - createComponent(); - - expect(wrapper.find(GlFormInputGroup).text()).toContain(`${GON_GITLAB_URL}/`); - }); - it('will have required attribute for required fields', () => { - mockGetRequest(); createComponent(); expect(findForkNameInput().attributes('required')).not.toBeUndefined(); - expect(findForkUrlInput().attributes('required')).not.toBeUndefined(); expect(findForkSlugInput().attributes('required')).not.toBeUndefined(); expect(findVisibilityRadioGroup().attributes('required')).not.toBeUndefined(); expect(findForkDescriptionTextarea().attributes('required')).toBeUndefined(); }); - describe('forks namespaces', () => { - beforeEach(() => { - mockGetRequest({ namespaces: MOCK_NAMESPACES_RESPONSE }); - createFullComponent(); - }); - - it('make GET request from endpoint', async () => { - await axios.waitForAll(); - - expect(axiosMock.history.get[0].url).toBe(DEFAULT_PROVIDE.endpoint); - }); - - it('generate default option', async () => { - await axios.waitForAll(); - - const optionsArray = findForkUrlInput().findAll('option'); - - expect(optionsArray.at(0).text()).toBe('Select a namespace'); - }); - - it('populate project url namespace options', async () => { - await axios.waitForAll(); - - const optionsArray = findForkUrlInput().findAll('option'); - - expect(optionsArray).toHaveLength(MOCK_NAMESPACES_RESPONSE.length + 1); - expect(optionsArray.at(1).text()).toBe(MOCK_NAMESPACES_RESPONSE[0].full_name); - expect(optionsArray.at(2).text()).toBe(MOCK_NAMESPACES_RESPONSE[1].full_name); - }); - - it('set namespaces in alphabetical order', async () => { - const namespace = { - name: 'three', - full_name: 'aaa/three', - id: 3, - }; - mockGetRequest({ - namespaces: [...MOCK_NAMESPACES_RESPONSE, namespace], - }); - createComponent(); - await axios.waitForAll(); - - expect(wrapper.vm.namespaces).toEqual([namespace, ...MOCK_NAMESPACES_RESPONSE]); - }); - }); - describe('project slug', () => { const projectPath = 'some other project slug'; beforeEach(() => { - mockGetRequest(); createComponent({ projectPath, }); @@ -232,10 +198,9 @@ describe('ForkForm component', () => { describe('visibility level', () => { it('displays the correct description', () => { - mockGetRequest(); createComponent(); - const formRadios = wrapper.findAll(GlFormRadio); + const formRadios = wrapper.findAllComponents(GlFormRadio); Object.keys(PROJECT_VISIBILITY_TYPE).forEach((visibilityType, index) => { expect(formRadios.at(index).text()).toBe(PROJECT_VISIBILITY_TYPE[visibilityType]); @@ -243,10 +208,9 @@ describe('ForkForm component', () => { }); it('displays all 3 visibility levels', () => { - mockGetRequest(); createComponent(); - expect(wrapper.findAll(GlFormRadio)).toHaveLength(3); + expect(wrapper.findAllComponents(GlFormRadio)).toHaveLength(3); }); describe('when the namespace is changed', () => { @@ -262,16 +226,12 @@ describe('ForkForm component', () => { }, ]; - beforeEach(() => { - mockGetRequest(); - }); - it('resets the visibility to default "private"', async () => { createFullComponent({ projectVisibility: 'public' }, { namespaces }); expect(wrapper.vm.form.fields.visibility.value).toBe('public'); - await findFormSelectOptions().at(1).setSelected(); + fillForm(); await nextTick(); expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true); @@ -280,8 +240,7 @@ describe('ForkForm component', () => { it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => { createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces }); - await findFormSelectOptions().at(1).setSelected(); - + fillForm(); await nextTick(); const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i }); @@ -315,8 +274,7 @@ describe('ForkForm component', () => { ${'public'} | ${[0, 20]} ${'public'} | ${[10, 20]} ${'public'} | ${[0, 10, 20]} - `('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => { - mockGetRequest(); + `('checks the correct radio button', ({ project, restrictedVisibilityLevels }) => { createFullComponent({ projectVisibility: project, restrictedVisibilityLevels, @@ -357,7 +315,7 @@ describe('ForkForm component', () => { ${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]} `( 'sets appropriate radio button disabled state', - async ({ + ({ project, namespace, privateIsDisabled, @@ -365,7 +323,6 @@ describe('ForkForm component', () => { publicIsDisabled, restrictedVisibilityLevels, }) => { - mockGetRequest(); createComponent( { projectVisibility: project, @@ -387,11 +344,9 @@ describe('ForkForm component', () => { const setupComponent = (fields = {}) => { jest.spyOn(urlUtility, 'redirectTo').mockImplementation(); - mockGetRequest(); createFullComponent( {}, { - namespaces: MOCK_NAMESPACES_RESPONSE, form: { state: true, ...fields, @@ -400,25 +355,21 @@ describe('ForkForm component', () => { ); }; - const selectedMockNamespaceIndex = 1; - const namespaceId = MOCK_NAMESPACES_RESPONSE[selectedMockNamespaceIndex].id; - - const fillForm = async () => { - const namespaceOptions = findForkUrlInput().findAll('option'); - - await namespaceOptions.at(selectedMockNamespaceIndex + 1).setSelected(); - }; + beforeEach(() => { + setupComponent(); + }); const submitForm = async () => { - await fillForm(); - const form = wrapper.find(GlForm); + fillForm(); + await nextTick(); + const form = wrapper.findComponent(GlForm); await form.trigger('submit'); await nextTick(); }; describe('with invalid form', () => { - it('does not make POST request', async () => { + it('does not make POST request', () => { jest.spyOn(axios, 'post'); setupComponent(); @@ -471,7 +422,7 @@ describe('ForkForm component', () => { description: projectDescription, id: projectId, name: projectName, - namespace_id: namespaceId, + namespace_id: selectedMockNamespace.id, path: projectPath, visibility: projectVisibility, }; diff --git a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js new file mode 100644 index 00000000000..1a88aebae32 --- /dev/null +++ b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js @@ -0,0 +1,177 @@ +import { + GlButton, + GlDropdown, + GlDropdownItem, + GlDropdownSectionHeader, + GlSearchBoxByType, + GlTruncate, +} from '@gitlab/ui'; +import { mount, shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import createFlash from '~/flash'; +import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql'; +import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue'; + +jest.mock('~/flash'); + +describe('ProjectNamespace component', () => { + let wrapper; + let originalGon; + + const data = { + project: { + __typename: 'Project', + id: 'gid://gitlab/Project/1', + forkTargets: { + nodes: [ + { + id: 'gid://gitlab/Group/21', + fullPath: 'flightjs', + name: 'Flight JS', + visibility: 'public', + }, + { + id: 'gid://gitlab/Namespace/4', + fullPath: 'root', + name: 'Administrator', + visibility: 'public', + }, + ], + }, + }, + }; + + const mockQueryResponse = jest.fn().mockResolvedValue({ data }); + + const emptyQueryResponse = { + project: { + __typename: 'Project', + id: 'gid://gitlab/Project/1', + forkTargets: { + nodes: [], + }, + }, + }; + + const mockQueryError = jest.fn().mockRejectedValue(new Error('Network error')); + + Vue.use(VueApollo); + + const gitlabUrl = 'https://gitlab.com'; + + const defaultProvide = { + projectFullPath: 'gitlab-org/project', + }; + + const mountComponent = ({ + provide = defaultProvide, + queryHandler = mockQueryResponse, + mountFn = shallowMount, + } = {}) => { + const requestHandlers = [[searchQuery, queryHandler]]; + const apolloProvider = createMockApollo(requestHandlers); + + wrapper = mountFn(ProjectNamespace, { + apolloProvider, + provide, + }); + }; + + const findButtonLabel = () => wrapper.findComponent(GlButton); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownText = () => wrapper.findComponent(GlTruncate); + const findInput = () => wrapper.findComponent(GlSearchBoxByType); + + const clickDropdownItem = async () => { + wrapper.findComponent(GlDropdownItem).vm.$emit('click'); + await nextTick(); + }; + + const showDropdown = () => { + findDropdown().vm.$emit('shown'); + }; + + beforeAll(() => { + originalGon = window.gon; + window.gon = { gitlab_url: gitlabUrl }; + }); + + afterAll(() => { + window.gon = originalGon; + wrapper.destroy(); + }); + + describe('Initial state', () => { + beforeEach(() => { + mountComponent({ mountFn: mount }); + jest.runOnlyPendingTimers(); + }); + + it('renders the root url as a label', () => { + expect(findButtonLabel().text()).toBe(`${gitlabUrl}/`); + expect(findButtonLabel().props('label')).toBe(true); + }); + + it('renders placeholder text', () => { + expect(findDropdownText().props('text')).toBe('Select a namespace'); + }); + }); + + describe('After user interactions', () => { + beforeEach(async () => { + mountComponent({ mountFn: mount }); + jest.runOnlyPendingTimers(); + await nextTick(); + showDropdown(); + }); + + it('focuses on the input when the dropdown is opened', () => { + const spy = jest.spyOn(findInput().vm, 'focusInput'); + showDropdown(); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('displays fetched namespaces', () => { + const listItems = wrapper.findAll('li'); + expect(listItems).toHaveLength(3); + expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Namespaces'); + expect(listItems.at(1).text()).toBe(data.project.forkTargets.nodes[0].fullPath); + expect(listItems.at(2).text()).toBe(data.project.forkTargets.nodes[1].fullPath); + }); + + it('sets the selected namespace', async () => { + const { fullPath } = data.project.forkTargets.nodes[0]; + await clickDropdownItem(); + expect(findDropdownText().props('text')).toBe(fullPath); + }); + }); + + describe('With empty query response', () => { + beforeEach(() => { + mountComponent({ queryHandler: emptyQueryResponse, mountFn: mount }); + jest.runOnlyPendingTimers(); + }); + + it('renders `No matches found`', () => { + expect(wrapper.find('li').text()).toBe('No matches found'); + }); + }); + + describe('With error while fetching data', () => { + beforeEach(async () => { + mountComponent({ queryHandler: mockQueryError }); + jest.runOnlyPendingTimers(); + await nextTick(); + }); + + it('creates a flash message and captures the error', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: 'Something went wrong while loading data. Please refresh the page to try again.', + captureError: true, + error: expect.any(Error), + }); + }); + }); +}); diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js index f272891919d..2f2edd6b025 100644 --- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js +++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js @@ -20,9 +20,9 @@ describe('Code Coverage', () => { const graphRef = 'master'; const graphCsvPath = 'url/'; - const findAlert = () => wrapper.find(GlAlert); - const findAreaChart = () => wrapper.find(GlAreaChart); - const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findAlert = () => wrapper.findComponent(GlAlert); + const findAreaChart = () => wrapper.findComponent(GlAreaChart); + const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findFirstDropdownItem = () => findAllDropdownItems().at(0); const findSecondDropdownItem = () => findAllDropdownItems().at(1); const findDownloadButton = () => wrapper.find('[data-testid="download-button"]'); @@ -142,7 +142,7 @@ describe('Code Coverage', () => { }); it('renders the dropdown with all custom names as options', () => { - expect(wrapper.find(GlDropdown).exists()).toBeDefined(); + expect(wrapper.findComponent(GlDropdown).exists()).toBeDefined(); expect(findAllDropdownItems()).toHaveLength(codeCoverageMockData.length); expect(findFirstDropdownItem().text()).toBe(codeCoverageMockData[0].group_name); }); diff --git a/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js b/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js new file mode 100644 index 00000000000..72077038dff --- /dev/null +++ b/spec/frontend/pages/projects/merge_requests/edit/update_form_spec.js @@ -0,0 +1,59 @@ +import { setHTMLFixture, resetHTMLFixture } from 'jest/__helpers__/fixtures'; +import initFormUpdate from '~/pages/projects/merge_requests/edit/update_form'; + +describe('Update form state', () => { + const submitEvent = new Event('submit', { + bubbles: true, + cancelable: true, + }); + + const submitForm = () => document.querySelector('.merge-request-form').dispatchEvent(submitEvent); + const hiddenInputs = () => document.querySelectorAll('input[type="hidden"]'); + const checkboxes = () => document.querySelectorAll('.js-form-update'); + + beforeEach(() => { + setHTMLFixture(` + <form class="merge-request-form"> + <div class="form-check"> + <input type="hidden" name="merge_request[force_remove_source_branch]" value="0" autocomplete="off"> + <input type="checkbox" name="merge_request[force_remove_source_branch]" id="merge_request_force_remove_source_branch" value="1" class="form-check-input js-form-update"> + </div> + <div class="form-check"> + <input type="hidden" name="merge_request[squash]" value="0" autocomplete="off"> + <input type="checkbox" name="merge_request[squash]" id="merge_request_squash" value="1" class="form-check-input js-form-update"> + </div> + </form>`); + initFormUpdate(); + }); + + afterEach(() => { + resetHTMLFixture(); + }); + + it('at initial state', () => { + submitForm(); + expect(hiddenInputs()).toHaveLength(2); + }); + + it('when one element is checked', () => { + checkboxes()[0].setAttribute('checked', true); + submitForm(); + expect(hiddenInputs()).toHaveLength(1); + }); + + it('when all elements are checked', () => { + checkboxes()[0].setAttribute('checked', true); + checkboxes()[1].setAttribute('checked', true); + submitForm(); + expect(hiddenInputs()).toHaveLength(0); + }); + + it('when checked and then unchecked', () => { + checkboxes()[0].setAttribute('checked', true); + checkboxes()[0].removeAttribute('checked'); + checkboxes()[1].setAttribute('checked', true); + checkboxes()[1].removeAttribute('checked'); + submitForm(); + expect(hiddenInputs()).toHaveLength(2); + }); +}); diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js index ca7f70f4434..a633332ab65 100644 --- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js +++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js @@ -21,7 +21,7 @@ describe('Pipeline Schedule Callout', () => { }; const findInnerContentOfCallout = () => wrapper.find('[data-testid="innerContent"]'); - const findDismissCalloutBtn = () => wrapper.find(GlButton); + const findDismissCalloutBtn = () => wrapper.findComponent(GlButton); describe(`when ${cookieKey} cookie is set`, () => { beforeEach(async () => { diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js index f908508c4b5..ed7d4ad269e 100644 --- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js +++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js @@ -5,8 +5,12 @@ import settingsPanel from '~/pages/projects/shared/permissions/components/settin import { featureAccessLevel, visibilityLevelDescriptions, - visibilityOptions, } from '~/pages/projects/shared/permissions/constants'; +import { + VISIBILITY_LEVEL_PRIVATE_INTEGER, + VISIBILITY_LEVEL_INTERNAL_INTEGER, + VISIBILITY_LEVEL_PUBLIC_INTEGER, +} from '~/visibility_level/constants'; import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue'; const defaultProps = { @@ -81,15 +85,17 @@ describe('Settings Panel', () => { }); }; - const findLFSSettingsRow = () => wrapper.find({ ref: 'git-lfs-settings' }); + const findLFSSettingsRow = () => wrapper.findComponent({ ref: 'git-lfs-settings' }); const findLFSSettingsMessage = () => findLFSSettingsRow().find('p'); - const findLFSFeatureToggle = () => findLFSSettingsRow().find(GlToggle); - const findRepositoryFeatureProjectRow = () => wrapper.find({ ref: 'repository-settings' }); + const findLFSFeatureToggle = () => findLFSSettingsRow().findComponent(GlToggle); + const findRepositoryFeatureProjectRow = () => + wrapper.findComponent({ ref: 'repository-settings' }); const findRepositoryFeatureSetting = () => - findRepositoryFeatureProjectRow().find(ProjectFeatureSetting); - const findProjectVisibilitySettings = () => wrapper.find({ ref: 'project-visibility-settings' }); - const findIssuesSettingsRow = () => wrapper.find({ ref: 'issues-settings' }); - const findAnalyticsRow = () => wrapper.find({ ref: 'analytics-settings' }); + findRepositoryFeatureProjectRow().findComponent(ProjectFeatureSetting); + const findProjectVisibilitySettings = () => + wrapper.findComponent({ ref: 'project-visibility-settings' }); + const findIssuesSettingsRow = () => wrapper.findComponent({ ref: 'issues-settings' }); + const findAnalyticsRow = () => wrapper.findComponent({ ref: 'analytics-settings' }); const findProjectVisibilityLevelInput = () => wrapper.find('[name="project[visibility_level]"]'); const findRequestAccessEnabledInput = () => wrapper.find('[name="project[request_access_enabled]"]'); @@ -99,35 +105,40 @@ describe('Settings Panel', () => { wrapper.find('[name="project[project_feature_attributes][forking_access_level]"]'); const findBuildsAccessLevelInput = () => wrapper.find('[name="project[project_feature_attributes][builds_access_level]"]'); - const findContainerRegistrySettings = () => wrapper.find({ ref: 'container-registry-settings' }); + const findContainerRegistrySettings = () => + wrapper.findComponent({ ref: 'container-registry-settings' }); const findContainerRegistryPublicNoteGlSprintfComponent = () => findContainerRegistrySettings().findComponent(GlSprintf); const findContainerRegistryAccessLevelInput = () => wrapper.find('[name="project[project_feature_attributes][container_registry_access_level]"]'); - const findPackageSettings = () => wrapper.find({ ref: 'package-settings' }); + const findPackageSettings = () => wrapper.findComponent({ ref: 'package-settings' }); const findPackageAccessLevel = () => wrapper.find('[data-testid="package-registry-access-level"]'); const findPackageAccessLevels = () => wrapper.find('[name="project[project_feature_attributes][package_registry_access_level]"]'); const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]'); - const findPagesSettings = () => wrapper.find({ ref: 'pages-settings' }); + const findPagesSettings = () => wrapper.findComponent({ ref: 'pages-settings' }); const findPagesAccessLevels = () => wrapper.find('[name="project[project_feature_attributes][pages_access_level]"]'); - const findEmailSettings = () => wrapper.find({ ref: 'email-settings' }); + const findEmailSettings = () => wrapper.findComponent({ ref: 'email-settings' }); const findShowDefaultAwardEmojis = () => wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]'); const findWarnAboutPuc = () => wrapper.find( 'input[name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"]', ); - const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' }); + const findMetricsVisibilitySettings = () => + wrapper.findComponent({ ref: 'metrics-visibility-settings' }); const findMetricsVisibilityInput = () => findMetricsVisibilitySettings().findComponent(ProjectFeatureSetting); - const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' }); + const findOperationsSettings = () => wrapper.findComponent({ ref: 'operations-settings' }); const findOperationsVisibilityInput = () => findOperationsSettings().findComponent(ProjectFeatureSetting); const findConfirmDangerButton = () => wrapper.findComponent(ConfirmDanger); const findEnvironmentsSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); + const findFeatureFlagsSettings = () => wrapper.findComponent({ ref: 'feature-flags-settings' }); + const findReleasesSettings = () => wrapper.findComponent({ ref: 'environments-settings' }); + const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' }); afterEach(() => { wrapper.destroy(); @@ -156,13 +167,13 @@ describe('Settings Panel', () => { }); it.each` - option | allowedOptions | disabled - ${visibilityOptions.PRIVATE} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.PRIVATE} | ${[visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${true} - ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.INTERNAL} | ${[visibilityOptions.PRIVATE, visibilityOptions.PUBLIC]} | ${true} - ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL, visibilityOptions.PUBLIC]} | ${false} - ${visibilityOptions.PUBLIC} | ${[visibilityOptions.PRIVATE, visibilityOptions.INTERNAL]} | ${true} + option | allowedOptions | disabled + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${true} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER, VISIBILITY_LEVEL_PUBLIC_INTEGER]} | ${false} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[VISIBILITY_LEVEL_PRIVATE_INTEGER, VISIBILITY_LEVEL_INTERNAL_INTEGER]} | ${true} `( 'sets disabled to $disabled for the visibility option $option when given $allowedOptions', ({ option, allowedOptions, disabled }) => { @@ -181,35 +192,37 @@ describe('Settings Panel', () => { it('should set the visibility level description based upon the selected visibility level', () => { wrapper = mountComponent({ stubs: { GlSprintf } }); - findProjectVisibilityLevelInput().setValue(visibilityOptions.INTERNAL); + findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_INTERNAL_INTEGER); expect(findProjectVisibilitySettings().text()).toContain( - visibilityLevelDescriptions[visibilityOptions.INTERNAL], + visibilityLevelDescriptions[VISIBILITY_LEVEL_INTERNAL_INTEGER], ); }); it('should show the request access checkbox if the visibility level is not private', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, }); expect(findRequestAccessEnabledInput().exists()).toBe(true); }); it('should not show the request access checkbox if the visibility level is private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, + }); expect(findRequestAccessEnabledInput().exists()).toBe(false); }); it('does not require confirmation if the visibility is reduced', async () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, }); expect(findConfirmDangerButton().exists()).toBe(false); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findConfirmDangerButton().exists()).toBe(false); }); @@ -217,7 +230,7 @@ describe('Settings Panel', () => { describe('showVisibilityConfirmModal=true', () => { beforeEach(() => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.INTERNAL }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_INTERNAL_INTEGER }, showVisibilityConfirmModal: true, }); }); @@ -225,7 +238,7 @@ describe('Settings Panel', () => { it('will render the confirmation dialog if the visibility is reduced', async () => { expect(findConfirmDangerButton().exists()).toBe(false); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findConfirmDangerButton().exists()).toBe(true); }); @@ -233,7 +246,7 @@ describe('Settings Panel', () => { it('emits the `confirm` event when the reduce visibility warning is confirmed', async () => { expect(wrapper.emitted('confirm')).toBeUndefined(); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); await findConfirmDangerButton().vm.$emit('confirm'); expect(wrapper.emitted('confirm')).toHaveLength(1); @@ -253,7 +266,9 @@ describe('Settings Panel', () => { describe('Repository', () => { it('should set the repository help text when the visibility level is set to private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PRIVATE } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, + }); expect(findRepositoryFeatureProjectRow().props('helpText')).toBe( 'View and edit files in this project.', @@ -261,7 +276,9 @@ describe('Settings Panel', () => { }); it('should set the repository help text with a read access warning when the visibility level is set to non-private', () => { - wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } }); + wrapper = mountComponent({ + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER }, + }); expect(findRepositoryFeatureProjectRow().props('helpText')).toBe( 'View and edit files in this project. Non-project members have only read access.', @@ -345,7 +362,7 @@ describe('Settings Panel', () => { it('should show the container registry public note if the visibility level is public and the registry is available', () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, containerRegistryAccessLevel: featureAccessLevel.EVERYONE, }, registryAvailable: true, @@ -360,7 +377,7 @@ describe('Settings Panel', () => { it('should hide the container registry public note if the visibility level is public but the registry is private', () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, containerRegistryAccessLevel: featureAccessLevel.PROJECT_MEMBERS, }, registryAvailable: true, @@ -371,7 +388,7 @@ describe('Settings Panel', () => { it('should hide the container registry public note if the visibility level is private and the registry is available', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.PRIVATE }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PRIVATE_INTEGER }, registryAvailable: true, }); @@ -380,7 +397,7 @@ describe('Settings Panel', () => { it('has label for the toggle', () => { wrapper = mountComponent({ - currentSettings: { visibilityLevel: visibilityOptions.PUBLIC }, + currentSettings: { visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER }, registryAvailable: true, }); @@ -569,10 +586,10 @@ describe('Settings Panel', () => { }); it.each` - visibilityLevel | output - ${visibilityOptions.PRIVATE} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]} - ${visibilityOptions.INTERNAL} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.PUBLIC} | ${[[30, 'Everyone']]} + visibilityLevel | output + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[[30, 'Everyone']]} `( 'renders correct options when visibilityLevel is $visibilityLevel', async ({ visibilityLevel, output }) => { @@ -589,23 +606,23 @@ describe('Settings Panel', () => { ); it.each` - initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PRIVATE} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.INTERNAL} | ${visibilityOptions.PUBLIC} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.PRIVATE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} - ${visibilityOptions.PUBLIC} | ${visibilityOptions.INTERNAL} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} + initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.EVERYONE} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${featureAccessLevel.EVERYONE} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.PROJECT_MEMBERS} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE} `( 'changes option from $initialPackageRegistryOption to $expectedPackageRegistryOption when visibilityLevel changed from $initialProjectVisibilityLevel to $newProjectVisibilityLevel', async ({ @@ -635,13 +652,13 @@ describe('Settings Panel', () => { describe('Pages', () => { it.each` - visibilityLevel | pagesAccessControlForced | output - ${visibilityOptions.PRIVATE} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.PRIVATE} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.INTERNAL} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.INTERNAL} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} - ${visibilityOptions.PUBLIC} | ${true} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access']]} - ${visibilityOptions.PUBLIC} | ${false} | ${[[visibilityOptions.INTERNAL, 'Only Project Members'], [visibilityOptions.PUBLIC, 'Everyone With Access'], [30, 'Everyone']]} + visibilityLevel | pagesAccessControlForced | output + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access']]} + ${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${[[VISIBILITY_LEVEL_INTERNAL_INTEGER, 'Only Project Members'], [VISIBILITY_LEVEL_PUBLIC_INTEGER, 'Everyone With Access'], [30, 'Everyone']]} `( 'renders correct options when pagesAccessControlForced is $pagesAccessControlForced and visibilityLevel is $visibilityLevel', async ({ visibilityLevel, pagesAccessControlForced, output }) => { @@ -760,13 +777,13 @@ describe('Settings Panel', () => { it('should reduce Metrics visibility level when visibility is set to private', async () => { wrapper = mountComponent({ currentSettings: { - visibilityLevel: visibilityOptions.PUBLIC, + visibilityLevel: VISIBILITY_LEVEL_PUBLIC_INTEGER, operationsAccessLevel: featureAccessLevel.EVERYONE, metricsDashboardAccessLevel: featureAccessLevel.EVERYONE, }, }); - await findProjectVisibilityLevelInput().setValue(visibilityOptions.PRIVATE); + await findProjectVisibilityLevelInput().setValue(VISIBILITY_LEVEL_PRIVATE_INTEGER); expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.PROJECT_MEMBERS); }); @@ -806,4 +823,78 @@ describe('Settings Panel', () => { }); }); }); + describe('Feature Flags', () => { + describe('with feature flag', () => { + it('should show the feature flags toggle', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + }); + + expect(findFeatureFlagsSettings().exists()).toBe(true); + }); + }); + describe('without feature flag', () => { + it('should not show the feature flags toggle', () => { + wrapper = mountComponent({}); + + expect(findFeatureFlagsSettings().exists()).toBe(false); + }); + }); + }); + describe('Releases', () => { + describe('with feature flag', () => { + it('should show the releases toggle', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + }); + + expect(findReleasesSettings().exists()).toBe(true); + }); + }); + describe('without feature flag', () => { + it('should not show the releases toggle', () => { + wrapper = mountComponent({}); + + expect(findReleasesSettings().exists()).toBe(false); + }); + }); + }); + describe('Monitor', () => { + const expectedAccessLevel = [ + [10, 'Only Project Members'], + [20, 'Everyone With Access'], + ]; + describe('with feature flag', () => { + it('shows Monitor toggle instead of Operations toggle', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + }); + + expect(findMonitorSettings().exists()).toBe(true); + expect(findOperationsSettings().exists()).toBe(false); + expect(findMonitorSettings().findComponent(ProjectFeatureSetting).props('options')).toEqual( + expectedAccessLevel, + ); + }); + it('when monitorAccessLevel is for project members, it is also for everyone', () => { + wrapper = mountComponent({ + glFeatures: { splitOperationsVisibilityPermissions: true }, + currentSettings: { monitorAccessLevel: featureAccessLevel.PROJECT_MEMBERS }, + }); + + expect(findMetricsVisibilityInput().props('value')).toBe(featureAccessLevel.EVERYONE); + }); + }); + describe('without feature flag', () => { + it('shows Operations toggle instead of Monitor toggle', () => { + wrapper = mountComponent({}); + + expect(findMonitorSettings().exists()).toBe(false); + expect(findOperationsSettings().exists()).toBe(true); + expect( + findOperationsSettings().findComponent(ProjectFeatureSetting).props('options'), + ).toEqual(expectedAccessLevel); + }); + }); + }); }); diff --git a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js index 108f816fe01..982c81b9272 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js @@ -38,7 +38,7 @@ describe('pages/shared/wikis/components/wiki_content', () => { const findGlAlert = () => wrapper.findComponent(GlAlert); const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); - const findContent = () => wrapper.find('[data-testid="wiki_page_content"]'); + const findContent = () => wrapper.find('[data-testid="wiki-page-content"]'); describe('when loading content', () => { beforeEach(() => { diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js index 204c48f8de1..b37d2f06191 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -39,7 +39,7 @@ describe('WikiForm', () => { const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link'); const findContentEditor = () => wrapper.findComponent(ContentEditor); const findClassicEditor = () => wrapper.findComponent(MarkdownField); - const findLocalStorageSync = () => wrapper.find(LocalStorageSync); + const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); const setFormat = (value) => { const format = findFormat(); @@ -302,19 +302,15 @@ describe('WikiForm', () => { }); it.each` - format | enabled | action + format | exists | action ${'markdown'} | ${true} | ${'displays'} ${'rdoc'} | ${false} | ${'hides'} ${'asciidoc'} | ${false} | ${'hides'} ${'org'} | ${false} | ${'hides'} - `('$action toggle editing mode button when format is $format', async ({ format, enabled }) => { + `('$action toggle editing mode button when format is $format', async ({ format, exists }) => { await setFormat(format); - expect(findToggleEditingModeButton().exists()).toBe(enabled); - }); - - it('displays toggle editing mode button', () => { - expect(findToggleEditingModeButton().exists()).toBe(true); + expect(findToggleEditingModeButton().exists()).toBe(exists); }); describe('when content editor is not active', () => { @@ -351,15 +347,8 @@ describe('WikiForm', () => { }); describe('when content editor is active', () => { - let mockContentEditor; - beforeEach(() => { createWrapper(); - mockContentEditor = { - getSerializedContent: jest.fn(), - setSerializedContent: jest.fn(), - }; - findToggleEditingModeButton().vm.$emit('input', 'richText'); }); @@ -368,14 +357,7 @@ describe('WikiForm', () => { }); describe('when clicking the toggle editing mode button', () => { - const contentEditorFakeSerializedContent = 'fake content'; - beforeEach(async () => { - mockContentEditor.getSerializedContent.mockReturnValueOnce( - contentEditorFakeSerializedContent, - ); - - findContentEditor().vm.$emit('initialized', mockContentEditor); await findToggleEditingModeButton().vm.$emit('input', 'source'); await nextTick(); }); @@ -387,10 +369,6 @@ describe('WikiForm', () => { it('displays the classic editor', () => { expect(findClassicEditor().exists()).toBe(true); }); - - it('updates the classic editor content field', () => { - expect(findContent().element.value).toBe(contentEditorFakeSerializedContent); - }); }); describe('when content editor is loading', () => { @@ -480,8 +458,14 @@ describe('WikiForm', () => { }); describe('when wiki content is updated', () => { + const updatedMarkdown = 'hello **world**'; + beforeEach(() => { - findContentEditor().vm.$emit('change', { empty: false }); + findContentEditor().vm.$emit('change', { + empty: false, + changed: true, + markdown: updatedMarkdown, + }); }); it('sets before unload warning', () => { @@ -512,16 +496,8 @@ describe('WikiForm', () => { }); }); - it('updates content from content editor on form submit', async () => { - // old value - expect(findContent().element.value).toBe(' My page content '); - - // wait for content editor to load - await waitForPromises(); - - await triggerFormSubmit(); - - expect(findContent().element.value).toBe('hello **world**'); + it('sets content field to the content editor updated markdown', async () => { + expect(findContent().element.value).toBe(updatedMarkdown); }); }); }); |