diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-13 00:10:33 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-13 00:10:33 +0300 |
commit | 8cdf31a1f97786973eb60564ef667e8416d1b1c8 (patch) | |
tree | da2c8f275dc32269bfadda39b74deb28137366c9 /spec | |
parent | bf1990164b801489b4475504701eefb66146e724 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
11 files changed, 237 insertions, 77 deletions
diff --git a/spec/finders/packages/pypi/packages_finder_spec.rb b/spec/finders/packages/pypi/packages_finder_spec.rb index a69c2317261..1a44fb99009 100644 --- a/spec/finders/packages/pypi/packages_finder_spec.rb +++ b/spec/finders/packages/pypi/packages_finder_spec.rb @@ -14,14 +14,14 @@ RSpec.describe Packages::Pypi::PackagesFinder do let(:package_name) { package2.name } - describe 'execute!' do - subject { described_class.new(user, scope, package_name: package_name).execute! } + describe 'execute' do + subject { described_class.new(user, scope, package_name: package_name).execute } shared_examples 'when no package is found' do context 'non-existing package' do let(:package_name) { 'none' } - it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } + it { expect(subject).to be_empty } end end @@ -29,7 +29,7 @@ RSpec.describe Packages::Pypi::PackagesFinder do context 'non-existing package' do let(:package_name) { package2.name.upcase.tr('-', '.') } - it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } + it { expect(subject).to be_empty } end end @@ -45,7 +45,7 @@ RSpec.describe Packages::Pypi::PackagesFinder do context 'within a group' do let(:scope) { group } - it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } + it { expect(subject).to be_empty } context 'user with access to only one project' do before do diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js index 5b6794cbc66..d516baf6f0f 100644 --- a/spec/frontend/content_editor/components/content_editor_spec.js +++ b/spec/frontend/content_editor/components/content_editor_spec.js @@ -1,93 +1,175 @@ -import { GlAlert } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; import { EditorContent } from '@tiptap/vue-2'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ContentEditor from '~/content_editor/components/content_editor.vue'; +import ContentEditorError from '~/content_editor/components/content_editor_error.vue'; +import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue'; +import EditorStateObserver from '~/content_editor/components/editor_state_observer.vue'; import TopToolbar from '~/content_editor/components/top_toolbar.vue'; -import { createContentEditor } from '~/content_editor/services/create_content_editor'; +import { + LOADING_CONTENT_EVENT, + LOADING_SUCCESS_EVENT, + LOADING_ERROR_EVENT, +} from '~/content_editor/constants'; +import { emitEditorEvent } from '../test_utils'; jest.mock('~/emoji'); describe('ContentEditor', () => { let wrapper; - let editor; + let contentEditor; + let renderMarkdown; + const uploadsPath = '/uploads'; const findEditorElement = () => wrapper.findByTestId('content-editor'); - const findErrorAlert = () => wrapper.findComponent(GlAlert); + const findEditorContent = () => wrapper.findComponent(EditorContent); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + + const createWrapper = (propsData = {}) => { + renderMarkdown = jest.fn(); - const createWrapper = async (contentEditor) => { wrapper = shallowMountExtended(ContentEditor, { propsData: { - contentEditor, + renderMarkdown, + uploadsPath, + ...propsData, + }, + stubs: { + EditorStateObserver, + ContentEditorProvider, + }, + listeners: { + initialized(editor) { + contentEditor = editor; + }, }, }); }; - beforeEach(() => { - editor = createContentEditor({ renderMarkdown: () => true }); - }); - afterEach(() => { wrapper.destroy(); }); - it('renders editor content component and attaches editor instance', () => { - createWrapper(editor); + it('triggers initialized event and provides contentEditor instance as event data', () => { + createWrapper(); - const editorContent = wrapper.findComponent(EditorContent); + expect(contentEditor).not.toBeFalsy(); + }); - expect(editorContent.props().editor).toBe(editor.tiptapEditor); + it('renders EditorContent component and provides tiptapEditor instance', () => { + createWrapper(); + + const editorContent = findEditorContent(); + + expect(editorContent.props().editor).toBe(contentEditor.tiptapEditor); expect(editorContent.classes()).toContain('md'); }); + it('renders ContentEditorProvider component', () => { + createWrapper(); + + expect(wrapper.findComponent(ContentEditorProvider).exists()).toBe(true); + }); + it('renders top toolbar component', () => { - createWrapper(editor); + createWrapper(); expect(wrapper.findComponent(TopToolbar).exists()).toBe(true); }); - it.each` - isFocused | classes - ${true} | ${['md-area', 'is-focused']} - ${false} | ${['md-area']} - `( - 'has $classes class selectors when tiptapEditor.isFocused = $isFocused', - ({ isFocused, classes }) => { - editor.tiptapEditor.isFocused = isFocused; - createWrapper(editor); + it('adds is-focused class when focus event is emitted', async () => { + createWrapper(); - expect(findEditorElement().classes()).toStrictEqual(classes); - }, - ); - - it('adds isFocused class when tiptapEditor is focused', () => { - editor.tiptapEditor.isFocused = true; - createWrapper(editor); + await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'focus' }); expect(findEditorElement().classes()).toContain('is-focused'); }); - describe('displaying error', () => { - const error = 'Content Editor error'; + it('removes is-focused class when blur event is emitted', async () => { + createWrapper(); + + await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'focus' }); + await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'blur' }); + + expect(findEditorElement().classes()).not.toContain('is-focused'); + }); + + it('emits change event when document is updated', async () => { + createWrapper(); + + await emitEditorEvent({ tiptapEditor: contentEditor.tiptapEditor, event: 'update' }); + + expect(wrapper.emitted('change')).toEqual([ + [ + { + empty: contentEditor.empty, + }, + ], + ]); + }); + + it('renders content_editor_error component', () => { + createWrapper(); + + expect(wrapper.findComponent(ContentEditorError).exists()).toBe(true); + }); + describe('when loading content', () => { beforeEach(async () => { - createWrapper(editor); + createWrapper(); - editor.tiptapEditor.emit('error', error); + contentEditor.emit(LOADING_CONTENT_EVENT); await nextTick(); }); - it('displays error notifications from the tiptap editor', () => { - expect(findErrorAlert().text()).toBe(error); + it('displays loading indicator', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + + it('hides EditorContent component', () => { + expect(findEditorContent().exists()).toBe(false); }); + }); - it('allows dismissing an error alert', async () => { - findErrorAlert().vm.$emit('dismiss'); + describe('when loading content succeeds', () => { + beforeEach(async () => { + createWrapper(); + contentEditor.emit(LOADING_CONTENT_EVENT); + await nextTick(); + contentEditor.emit(LOADING_SUCCESS_EVENT); await nextTick(); + }); + + it('hides loading indicator', () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + + it('displays EditorContent component', () => { + expect(findEditorContent().exists()).toBe(true); + }); + }); + + describe('when loading content fails', () => { + const error = 'error'; + + beforeEach(async () => { + createWrapper(); + + contentEditor.emit(LOADING_CONTENT_EVENT); + await nextTick(); + contentEditor.emit(LOADING_ERROR_EVENT, error); + await nextTick(); + }); + + it('hides loading indicator', () => { + expect(findLoadingIcon().exists()).toBe(false); + }); - expect(findErrorAlert().exists()).toBe(false); + it('displays EditorContent component', () => { + expect(findEditorContent().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js index d87a1459b50..1334b1ddaad 100644 --- a/spec/frontend/content_editor/extensions/attachment_spec.js +++ b/spec/frontend/content_editor/extensions/attachment_spec.js @@ -10,7 +10,7 @@ import httpStatus from '~/lib/utils/http_status'; import { loadMarkdownApiResult } from '../markdown_processing_examples'; import { createTestEditor, createDocBuilder } from '../test_utils'; -describe('content_editor/extensions/image', () => { +describe('content_editor/extensions/attachment', () => { let tiptapEditor; let eq; let doc; @@ -144,8 +144,8 @@ describe('content_editor/extensions/image', () => { it('emits an error event that includes an error message', (done) => { tiptapEditor.commands.uploadAttachment({ file: imageFile }); - tiptapEditor.on('error', (message) => { - expect(message).toBe('An error occurred while uploading the image. Please try again.'); + tiptapEditor.on('error', ({ error }) => { + expect(error).toBe('An error occurred while uploading the image. Please try again.'); done(); }); }); @@ -224,8 +224,8 @@ describe('content_editor/extensions/image', () => { it('emits an error event that includes an error message', (done) => { tiptapEditor.commands.uploadAttachment({ file: attachmentFile }); - tiptapEditor.on('error', (message) => { - expect(message).toBe('An error occurred while uploading the file. Please try again.'); + tiptapEditor.on('error', ({ error }) => { + expect(error).toBe('An error occurred while uploading the file. Please try again.'); done(); }); }); diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js index 8580d3249b9..e48687f1548 100644 --- a/spec/frontend/content_editor/services/content_editor_spec.js +++ b/spec/frontend/content_editor/services/content_editor_spec.js @@ -13,10 +13,22 @@ describe('content_editor/services/content_editor', () => { beforeEach(() => { const tiptapEditor = createTestEditor(); + jest.spyOn(tiptapEditor, 'destroy'); + serializer = { deserialize: jest.fn() }; contentEditor = new ContentEditor({ tiptapEditor, serializer }); }); + describe('.dispose', () => { + it('destroys the tiptapEditor', () => { + expect(contentEditor.tiptapEditor.destroy).not.toHaveBeenCalled(); + + contentEditor.dispose(); + + expect(contentEditor.tiptapEditor.destroy).toHaveBeenCalled(); + }); + }); + describe('when setSerializedContent succeeds', () => { beforeEach(() => { serializer.deserialize.mockResolvedValueOnce(''); 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 97cb70dbd62..082a8977710 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -352,11 +352,6 @@ describe('WikiForm', () => { await waitForPromises(); }); - it('editor is shown in a perpetual loading state', () => { - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); - expect(wrapper.findComponent(ContentEditor).exists()).toBe(false); - }); - it('disables the submit button', () => { expect(findSubmitButton().props('disabled')).toBe(true); }); diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb index 99b52236771..ae0c0f53acd 100644 --- a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb +++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do let_it_be(:helper) { Class.new.include(described_class).new } - describe 'redirect_registry_request' do + describe '#redirect_registry_request' do using RSpec::Parameterized::TableSyntax let(:options) { {} } @@ -13,7 +13,7 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } } before do - allow(helper).to receive(:options).and_return(for: API::NpmInstancePackages) + allow(helper).to receive(:options).and_return(for: described_class) end shared_examples 'executing fallback' do @@ -34,38 +34,66 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do subject - expect_snowplow_event(category: 'API::NpmInstancePackages', action: 'npm_request_forward') + expect_snowplow_event(category: described_class.to_s, action: "#{package_type}_request_forward") end end - context 'with npm packages' do - let(:package_type) { :npm } + %i[npm pypi].each do |forwardable_package_type| + context "with #{forwardable_package_type} packages" do + include_context 'dependency proxy helpers context' - where(:application_setting, :forward_to_registry, :example_name) do - true | true | 'executing redirect' - true | false | 'executing fallback' - false | true | 'executing fallback' - false | false | 'executing fallback' - end + let(:package_type) { forwardable_package_type } - with_them do - before do - stub_application_setting(npm_package_requests_forwarding: application_setting) + where(:application_setting, :forward_to_registry, :example_name) do + true | true | 'executing redirect' + true | false | 'executing fallback' + false | true | 'executing fallback' + false | false | 'executing fallback' end - it_behaves_like params[:example_name] + with_them do + before do + allow_fetch_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: application_setting) + end + + it_behaves_like params[:example_name] + end end end - context 'with non-forwardable packages' do + context 'with non-forwardable package type' do let(:forward_to_registry) { true } before do stub_application_setting(npm_package_requests_forwarding: true) + stub_application_setting(pypi_package_requests_forwarding: true) end - Packages::Package.package_types.keys.without('npm').each do |pkg_type| + Packages::Package.package_types.keys.without('npm', 'pypi').each do |pkg_type| context "#{pkg_type}" do + let(:package_type) { pkg_type.to_sym } + + it 'raises an error' do + expect { subject }.to raise_error(ArgumentError, "Can't find application setting for package_type #{package_type}") + end + end + end + end + + describe '#registry_url' do + subject { helper.registry_url(package_type, package_name: 'test') } + + where(:package_type, :expected_result) do + :npm | 'https://registry.npmjs.org/test' + :pypi | 'https://pypi.org/simple/test/' + end + + with_them do + it { is_expected.to eq(expected_result) } + end + + Packages::Package.package_types.keys.without('npm', 'pypi').each do |pkg_type| + context "with non-forwardable package type #{pkg_type}" do let(:package_type) { pkg_type } it 'raises an error' do diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index a3200ef6a5b..8df2460a2b6 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -23,7 +23,8 @@ RSpec.describe API::PypiPackages do subject { get api(url), headers: headers } describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do - let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package.name}" } + let(:package_name) { package.name } + let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package_name}" } let(:snowplow_gitlab_standard_context) { {} } it_behaves_like 'pypi simple API endpoint' @@ -40,7 +41,7 @@ RSpec.describe API::PypiPackages do it_behaves_like 'deploy token for package GET requests' context 'with group path as id' do - let(:url) { "/groups/#{CGI.escape(group.full_path)}/-/packages/pypi/simple/#{package.name}" } + let(:url) { "/groups/#{CGI.escape(group.full_path)}/-/packages/pypi/simple/#{package_name}"} it_behaves_like 'deploy token for package GET requests' end @@ -60,7 +61,8 @@ RSpec.describe API::PypiPackages do end describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do - let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" } + let(:package_name) { package.name } + let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package_name}" } let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } it_behaves_like 'pypi simple API endpoint' diff --git a/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb new file mode 100644 index 00000000000..7c8b6250d24 --- /dev/null +++ b/spec/support/shared_contexts/lib/api/helpers/packages/dependency_proxy_helpers_shared_context.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_context 'dependency proxy helpers context' do + def allow_fetch_application_setting(attribute:, return_value:) + attributes = double + allow(::Gitlab::CurrentSettings.current_application_settings).to receive(:attributes).and_return(attributes) + allow(attributes).to receive(:fetch).with(attribute, false).and_return(return_value) + end +end diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb index c15c59e1a1d..0390e60747f 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb @@ -46,6 +46,8 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| end shared_examples 'handling all conditions' do + include_context 'dependency proxy helpers context' + where(:auth, :package_name_type, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do nil | :scoped_naming_convention | true | :public | nil | :accept | :ok nil | :scoped_naming_convention | false | :public | nil | :accept | :ok @@ -243,7 +245,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| project.send("add_#{user_role}", user) if user_role project.update!(visibility: visibility.to_s) package.update!(name: package_name) unless package_name == 'non-existing-package' - stub_application_setting(npm_package_requests_forwarding: request_forward) + allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward) end example_name = "#{params[:expected_result]} metadata request" diff --git a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb index e6b3dc74b74..86b6975bf9f 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_tags_shared_examples.rb @@ -10,9 +10,10 @@ end RSpec.shared_examples 'accept package tags request' do |status:| using RSpec::Parameterized::TableSyntax + include_context 'dependency proxy helpers context' before do - stub_application_setting(npm_package_requests_forwarding: false) + allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: false) end context 'with valid package name' do diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index 8a351226123..ed6d9ed43c8 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -228,6 +228,35 @@ RSpec.shared_examples 'pypi simple API endpoint' do it_behaves_like 'PyPI package versions', :developer, :success end + + context 'package request forward' do + include_context 'dependency proxy helpers context' + + where(:forward, :package_in_project, :shared_examples_name, :expected_status) do + true | true | 'PyPI package versions' | :success + true | false | 'process PyPI api request' | :redirect + false | true | 'PyPI package versions' | :success + false | false | 'process PyPI api request' | :not_found + end + + with_them do + let_it_be(:package) { create(:pypi_package, project: project, name: 'foobar') } + + let(:package_name) do + if package_in_project + 'foobar' + else + 'barfoo' + end + end + + before do + allow_fetch_application_setting(attribute: "pypi_package_requests_forwarding", return_value: forward) + end + + it_behaves_like params[:shared_examples_name], :reporter, params[:expected_status] + end + end end RSpec.shared_examples 'pypi file download endpoint' do |