Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 21:14:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 21:14:27 +0300
commitf6c037b73c90ac251625c17720e6b8307c1698cc (patch)
tree26a287a09ae414da75f7e383dd1eaae5d3704ca5 /spec
parentbf0d6692fc4c16205cc49b8b87b7b7e0daa97c63 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js3
-rw-r--r--spec/frontend/dropzone_input_spec.js29
-rw-r--r--spec/frontend/issuable_list/components/issuable_item_spec.js105
-rw-r--r--spec/frontend/issues_list/components/issues_list_app_spec.js51
-rw-r--r--spec/frontend/issues_list/mock_data.js1
-rw-r--r--spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap2
-rw-r--r--spec/helpers/issues_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_enq_spec.rb93
-rw-r--r--spec/lib/sidebars/projects/menus/shimo_menu_spec.rb44
-rw-r--r--spec/models/integrations/shimo_spec.rb22
-rw-r--r--spec/requests/projects/integrations/shimos_controller_spec.rb37
13 files changed, 258 insertions, 135 deletions
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index cfd93c2df10..c41d9016e52 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -28,7 +28,6 @@ import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
import TaskItem from '~/content_editor/extensions/task_item';
import TaskList from '~/content_editor/extensions/task_list';
-import Text from '~/content_editor/extensions/text';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
import { createTestEditor, createDocBuilder } from '../test_utils';
@@ -58,7 +57,6 @@ const tiptapEditor = createTestEditor({
Link,
ListItem,
OrderedList,
- Paragraph,
Strike,
Table,
TableCell,
@@ -66,7 +64,6 @@ const tiptapEditor = createTestEditor({
TableRow,
TaskItem,
TaskList,
- Text,
],
});
diff --git a/spec/frontend/dropzone_input_spec.js b/spec/frontend/dropzone_input_spec.js
index 12e10f7c5f4..11414e8890d 100644
--- a/spec/frontend/dropzone_input_spec.js
+++ b/spec/frontend/dropzone_input_spec.js
@@ -32,6 +32,8 @@ describe('dropzone_input', () => {
});
describe('handlePaste', () => {
+ let form;
+
const triggerPasteEvent = (clipboardData = {}) => {
const event = $.Event('paste');
const origEvent = new Event('paste');
@@ -45,11 +47,15 @@ describe('dropzone_input', () => {
beforeEach(() => {
loadFixtures('issues/new-issue.html');
- const form = $('#new_issue');
+ form = $('#new_issue');
form.data('uploads-path', TEST_UPLOAD_PATH);
dropzoneInput(form);
});
+ afterEach(() => {
+ form = null;
+ });
+
it('pastes Markdown tables', () => {
jest.spyOn(PasteMarkdownTable.prototype, 'isTable');
jest.spyOn(PasteMarkdownTable.prototype, 'convertToTableMarkdown');
@@ -86,6 +92,27 @@ describe('dropzone_input', () => {
expect(axiosMock.history.post[0].data.get('file').name).toHaveLength(246);
});
+ it('disables generated image file when clipboardData have both image and text', () => {
+ const TEST_PLAIN_TEXT = 'This wording is a plain text.';
+ triggerPasteEvent({
+ types: ['text/plain', 'Files'],
+ getData: () => TEST_PLAIN_TEXT,
+ items: [
+ {
+ kind: 'text',
+ type: 'text/plain',
+ },
+ {
+ kind: 'file',
+ type: 'image/png',
+ getAsFile: () => new Blob(),
+ },
+ ],
+ });
+
+ expect(form.find('.js-gfm-input')[0].value).toBe('');
+ });
+
it('display original file name in comment box', async () => {
const axiosMock = new MockAdapter(axios);
triggerPasteEvent({
diff --git a/spec/frontend/issuable_list/components/issuable_item_spec.js b/spec/frontend/issuable_list/components/issuable_item_spec.js
index ac3bf7f3269..a4d90613ca6 100644
--- a/spec/frontend/issuable_list/components/issuable_item_spec.js
+++ b/spec/frontend/issuable_list/components/issuable_item_spec.js
@@ -1,19 +1,25 @@
import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
+import { shallowMountExtended as shallowMount } from 'helpers/vue_test_utils_helper';
import IssuableItem from '~/issuable_list/components/issuable_item.vue';
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data';
-const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots = {} } = {}) =>
+const createComponent = ({
+ issuableSymbol = '#',
+ issuable = mockIssuable,
+ enableLabelPermalinks = true,
+ showCheckbox = true,
+ slots = {},
+} = {}) =>
shallowMount(IssuableItem, {
propsData: {
issuableSymbol,
issuable,
- enableLabelPermalinks: true,
+ enableLabelPermalinks,
showDiscussions: true,
- showCheckbox: false,
+ showCheckbox,
},
slots,
stubs: {
@@ -34,7 +40,6 @@ describe('IssuableItem', () => {
beforeEach(() => {
gon.gitlab_url = MOCK_GITLAB_URL;
- wrapper = createComponent();
});
afterEach(() => {
@@ -45,6 +50,8 @@ describe('IssuableItem', () => {
describe('computed', () => {
describe('author', () => {
it('returns `issuable.author` reference', () => {
+ wrapper = createComponent();
+
expect(wrapper.vm.author).toEqual(mockIssuable.author);
});
});
@@ -59,7 +66,7 @@ describe('IssuableItem', () => {
`(
'returns $returnValue when value of `issuable.author.id` is $authorId',
async ({ authorId, returnValue }) => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
author: {
@@ -86,7 +93,7 @@ describe('IssuableItem', () => {
`(
'returns $returnValue when `issuable.webUrl` is $urlType',
async ({ issuableWebUrl, returnValue }) => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
webUrl: issuableWebUrl,
@@ -102,11 +109,13 @@ describe('IssuableItem', () => {
describe('labels', () => {
it('returns `issuable.labels.nodes` reference when it is available', () => {
+ wrapper = createComponent();
+
expect(wrapper.vm.labels).toEqual(mockLabels);
});
it('returns `issuable.labels` reference when it is available', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
labels: mockLabels,
@@ -119,7 +128,7 @@ describe('IssuableItem', () => {
});
it('returns empty array when none of `issuable.labels.nodes` or `issuable.labels` are available', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
labels: null,
@@ -134,12 +143,16 @@ describe('IssuableItem', () => {
describe('assignees', () => {
it('returns `issuable.assignees` reference when it is available', () => {
+ wrapper = createComponent();
+
expect(wrapper.vm.assignees).toBe(mockIssuable.assignees);
});
});
describe('updatedAt', () => {
it('returns string containing timeago string based on `issuable.updatedAt`', () => {
+ wrapper = createComponent();
+
expect(wrapper.vm.updatedAt).toContain('updated');
expect(wrapper.vm.updatedAt).toContain('ago');
});
@@ -155,7 +168,7 @@ describe('IssuableItem', () => {
`(
'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount',
({ userDiscussionsCount, returnValue }) => {
- const wrapperWithDiscussions = createComponent({
+ wrapper = createComponent({
issuableSymbol: '#',
issuable: {
...mockIssuable,
@@ -163,9 +176,7 @@ describe('IssuableItem', () => {
},
});
- expect(wrapperWithDiscussions.vm.showDiscussions).toBe(returnValue);
-
- wrapperWithDiscussions.destroy();
+ expect(wrapper.findByTestId('issuable-discussions').exists()).toBe(returnValue);
},
);
});
@@ -180,6 +191,8 @@ describe('IssuableItem', () => {
`(
'return $returnValue when provided label param is a $labelType label',
({ label, returnValue }) => {
+ wrapper = createComponent();
+
expect(wrapper.vm.scopedLabel(label)).toBe(returnValue);
},
);
@@ -191,19 +204,23 @@ describe('IssuableItem', () => {
${{ title: 'foo' }} | ${'title'} | ${'foo'}
${{ name: 'foo' }} | ${'name'} | ${'foo'}
`('returns string value of `label.$propWithTitle`', ({ label, returnValue }) => {
+ wrapper = createComponent();
+
expect(wrapper.vm.labelTitle(label)).toBe(returnValue);
});
});
describe('labelTarget', () => {
it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => {
+ wrapper = createComponent();
+
expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe(
'?label_name[]=Documentation%20Update',
);
});
it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
enableLabelPermalinks: false,
});
@@ -223,7 +240,7 @@ describe('IssuableItem', () => {
`(
'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`',
async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
webUrl,
@@ -243,7 +260,7 @@ describe('IssuableItem', () => {
);
it('renders checkbox when `showCheckbox` prop is true', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
showCheckbox: true,
});
@@ -262,7 +279,7 @@ describe('IssuableItem', () => {
});
it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
webUrl: 'http://jira.atlassian.net/browse/IG-1',
@@ -277,7 +294,7 @@ describe('IssuableItem', () => {
});
it('renders issuable confidential icon when issuable is confidential', async () => {
- wrapper.setProps({
+ wrapper = createComponent({
issuable: {
...mockIssuable,
confidential: true,
@@ -296,7 +313,21 @@ describe('IssuableItem', () => {
});
});
+ it('renders spam icon when issuable is hidden', async () => {
+ wrapper = createComponent({ issuable: { ...mockIssuable, hidden: true } });
+
+ const hiddenIcon = wrapper.findComponent(GlIcon);
+
+ expect(hiddenIcon.props('name')).toBe('spam');
+ expect(hiddenIcon.attributes()).toMatchObject({
+ title: 'This issue is hidden because its author has been banned',
+ arialabel: 'Hidden',
+ });
+ });
+
it('renders task status', () => {
+ wrapper = createComponent();
+
const taskStatus = wrapper.find('[data-testid="task-status"]');
const expected = `${mockIssuable.taskCompletionStatus.completedCount} of ${mockIssuable.taskCompletionStatus.count} tasks completed`;
@@ -304,6 +335,8 @@ describe('IssuableItem', () => {
});
it('renders issuable reference', () => {
+ wrapper = createComponent();
+
const referenceEl = wrapper.find('[data-testid="issuable-reference"]');
expect(referenceEl.exists()).toBe(true);
@@ -311,7 +344,7 @@ describe('IssuableItem', () => {
});
it('renders issuable reference via slot', () => {
- const wrapperWithRefSlot = createComponent({
+ wrapper = createComponent({
issuableSymbol: '#',
issuable: mockIssuable,
slots: {
@@ -320,15 +353,15 @@ describe('IssuableItem', () => {
`,
},
});
- const referenceEl = wrapperWithRefSlot.find('.js-reference');
+ const referenceEl = wrapper.find('.js-reference');
expect(referenceEl.exists()).toBe(true);
expect(referenceEl.text()).toBe(`${mockIssuable.iid}`);
-
- wrapperWithRefSlot.destroy();
});
it('renders issuable createdAt info', () => {
+ wrapper = createComponent();
+
const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]');
expect(createdAtEl.exists()).toBe(true);
@@ -337,6 +370,8 @@ describe('IssuableItem', () => {
});
it('renders issuable author info', () => {
+ wrapper = createComponent();
+
const authorEl = wrapper.find('[data-testid="issuable-author"]');
expect(authorEl.exists()).toBe(true);
@@ -351,7 +386,7 @@ describe('IssuableItem', () => {
});
it('renders issuable author info via slot', () => {
- const wrapperWithAuthorSlot = createComponent({
+ wrapper = createComponent({
issuableSymbol: '#',
issuable: mockIssuable,
slots: {
@@ -360,16 +395,14 @@ describe('IssuableItem', () => {
`,
},
});
- const authorEl = wrapperWithAuthorSlot.find('.js-author');
+ const authorEl = wrapper.find('.js-author');
expect(authorEl.exists()).toBe(true);
expect(authorEl.text()).toBe(mockAuthor.name);
-
- wrapperWithAuthorSlot.destroy();
});
it('renders timeframe via slot', () => {
- const wrapperWithTimeframeSlot = createComponent({
+ wrapper = createComponent({
issuableSymbol: '#',
issuable: mockIssuable,
slots: {
@@ -378,15 +411,15 @@ describe('IssuableItem', () => {
`,
},
});
- const timeframeEl = wrapperWithTimeframeSlot.find('.js-timeframe');
+ const timeframeEl = wrapper.find('.js-timeframe');
expect(timeframeEl.exists()).toBe(true);
expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020');
-
- wrapperWithTimeframeSlot.destroy();
});
it('renders gl-label component for each label present within `issuable` prop', () => {
+ wrapper = createComponent();
+
const labelsEl = wrapper.findAll(GlLabel);
expect(labelsEl.exists()).toBe(true);
@@ -402,7 +435,7 @@ describe('IssuableItem', () => {
});
it('renders issuable status via slot', () => {
- const wrapperWithStatusSlot = createComponent({
+ wrapper = createComponent({
issuableSymbol: '#',
issuable: mockIssuable,
slots: {
@@ -411,15 +444,15 @@ describe('IssuableItem', () => {
`,
},
});
- const statusEl = wrapperWithStatusSlot.find('.js-status');
+ const statusEl = wrapper.find('.js-status');
expect(statusEl.exists()).toBe(true);
expect(statusEl.text()).toBe(`${mockIssuable.state}`);
-
- wrapperWithStatusSlot.destroy();
});
it('renders discussions count', () => {
+ wrapper = createComponent();
+
const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]');
expect(discussionsEl.exists()).toBe(true);
@@ -432,6 +465,8 @@ describe('IssuableItem', () => {
});
it('renders issuable-assignees component', () => {
+ wrapper = createComponent();
+
const assigneesEl = wrapper.find(IssuableAssignees);
expect(assigneesEl.exists()).toBe(true);
@@ -443,6 +478,8 @@ describe('IssuableItem', () => {
});
it('renders issuable updatedAt info', () => {
+ wrapper = createComponent();
+
const updatedAtEl = wrapper.find('[data-testid="issuable-updated-at"]');
expect(updatedAtEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js
index 3f52c7b4afe..c398acf081b 100644
--- a/spec/frontend/issues_list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues_list/components/issues_list_app_spec.js
@@ -17,7 +17,7 @@ import {
locationSearch,
urlParams,
} from 'jest/issues_list/mock_data';
-import createFlash from '~/flash';
+import createFlash, { FLASH_TYPES } from '~/flash';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
@@ -29,6 +29,8 @@ import {
CREATED_DESC,
DUE_DATE_OVERDUE,
PARAM_DUE_DATE,
+ RELATIVE_POSITION,
+ RELATIVE_POSITION_ASC,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL,
@@ -314,6 +316,29 @@ describe('IssuesListApp component', () => {
},
});
});
+
+ describe('when issue repositioning is disabled and the sort is manual', () => {
+ beforeEach(() => {
+ setWindowLocation(`?sort=${RELATIVE_POSITION}`);
+ wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } });
+ });
+
+ it('changes the sort to the default of created descending', () => {
+ expect(findIssuableList().props()).toMatchObject({
+ initialSortBy: CREATED_DESC,
+ urlParams: {
+ sort: urlSortParams[CREATED_DESC],
+ },
+ });
+ });
+
+ it('shows an alert to tell the user that manual reordering is disabled', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: IssuesListApp.i18n.issueRepositioningMessage,
+ type: FLASH_TYPES.NOTICE,
+ });
+ });
+ });
});
describe('state', () => {
@@ -762,6 +787,30 @@ describe('IssuesListApp component', () => {
});
},
);
+
+ describe('when issue repositioning is disabled', () => {
+ const initialSort = CREATED_DESC;
+
+ beforeEach(() => {
+ setWindowLocation(`?sort=${initialSort}`);
+ wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } });
+
+ findIssuableList().vm.$emit('sort', RELATIVE_POSITION_ASC);
+ });
+
+ it('does not update the sort to manual', () => {
+ expect(findIssuableList().props('urlParams')).toMatchObject({
+ sort: urlSortParams[initialSort],
+ });
+ });
+
+ it('shows an alert to tell the user that manual reordering is disabled', () => {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: IssuesListApp.i18n.issueRepositioningMessage,
+ type: FLASH_TYPES.NOTICE,
+ });
+ });
+ });
});
describe('when "update-legacy-bulk-edit" event is emitted by IssuableList', () => {
diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js
index 19a8af4d9c2..d408316a063 100644
--- a/spec/frontend/issues_list/mock_data.js
+++ b/spec/frontend/issues_list/mock_data.js
@@ -22,6 +22,7 @@ export const getIssuesQueryResponse = {
createdAt: '2021-05-22T04:08:01Z',
downvotes: 2,
dueDate: '2021-05-29',
+ hidden: false,
humanTimeEstimate: null,
mergeRequestsCount: false,
moved: false,
diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
index b576f1b2553..7e3ff734981 100644
--- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
+++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap
@@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = `
data-qa-selector="package_row"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-3 gl-px-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
index c95538546c1..7aa42a1f1e5 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
@@ -5,7 +5,7 @@ exports[`VersionRow renders 1`] = `
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-3 gl-px-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
index 2f2be797251..bb65db807f4 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/__snapshots__/package_list_row_spec.js.snap
@@ -6,7 +6,7 @@ exports[`packages_list_row renders 1`] = `
data-qa-selector="package_row"
>
<div
- class="gl-display-flex gl-align-items-center gl-py-3 gl-px-5"
+ class="gl-display-flex gl-align-items-center gl-py-3"
>
<!---->
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 43b27dded3b..31e19080efe 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -302,6 +302,7 @@ RSpec.describe IssuesHelper do
allow(helper).to receive(:can?).and_return(true)
allow(helper).to receive(:image_path).and_return('#')
allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#')
+ allow(helper).to receive(:issue_repositioning_disabled?).and_return(true)
allow(helper).to receive(:url_for).and_return('#')
expected = {
@@ -318,6 +319,7 @@ RSpec.describe IssuesHelper do
has_any_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#',
initial_email: project.new_issuable_address(current_user, 'issue'),
+ is_issue_repositioning_disabled: 'true',
is_project: 'true',
is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
diff --git a/spec/lib/gitlab/sidekiq_enq_spec.rb b/spec/lib/gitlab/sidekiq_enq_spec.rb
deleted file mode 100644
index 6903f01bf5f..00000000000
--- a/spec/lib/gitlab/sidekiq_enq_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::SidekiqEnq, :clean_gitlab_redis_queues do
- let(:retry_set) { Sidekiq::Scheduled::SETS.first }
- let(:schedule_set) { Sidekiq::Scheduled::SETS.last }
-
- around do |example|
- freeze_time { example.run }
- end
-
- shared_examples 'finds jobs that are due and enqueues them' do
- before do
- Sidekiq.redis do |redis|
- redis.zadd(retry_set, (Time.current - 1.day).to_f.to_s, '{"jid": 1}')
- redis.zadd(retry_set, Time.current.to_f.to_s, '{"jid": 2}')
- redis.zadd(retry_set, (Time.current + 1.day).to_f.to_s, '{"jid": 3}')
-
- redis.zadd(schedule_set, (Time.current - 1.day).to_f.to_s, '{"jid": 4}')
- redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 5}')
- redis.zadd(schedule_set, (Time.current + 1.day).to_f.to_s, '{"jid": 6}')
- end
- end
-
- it 'enqueues jobs that are due' do
- expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 1 })
- expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 2 })
- expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 4 })
- expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 5 })
-
- Gitlab::SidekiqEnq.new.enqueue_jobs
-
- Sidekiq.redis do |redis|
- expect(redis.zscan_each(retry_set).map(&:first)).to contain_exactly('{"jid": 3}')
- expect(redis.zscan_each(schedule_set).map(&:first)).to contain_exactly('{"jid": 6}')
- end
- end
- end
-
- context 'when atomic_sidekiq_scheduler is disabled' do
- before do
- stub_feature_flags(atomic_sidekiq_scheduler: false)
- end
-
- it_behaves_like 'finds jobs that are due and enqueues them'
-
- context 'when ZRANGEBYSCORE returns a job that is already removed by another process' do
- before do
- Sidekiq.redis do |redis|
- redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 1}')
-
- allow(redis).to receive(:zrangebyscore).and_wrap_original do |m, *args, **kwargs|
- m.call(*args, **kwargs).tap do |jobs|
- redis.zrem(schedule_set, jobs.first) if args[0] == schedule_set && jobs.first
- end
- end
- end
- end
-
- it 'calls ZREM but does not enqueue the job' do
- Sidekiq.redis do |redis|
- expect(redis).to receive(:zrem).with(schedule_set, '{"jid": 1}').twice.and_call_original
- end
- expect(Sidekiq::Client).not_to receive(:push)
-
- Gitlab::SidekiqEnq.new.enqueue_jobs
- end
- end
- end
-
- context 'when atomic_sidekiq_scheduler is enabled' do
- before do
- stub_feature_flags(atomic_sidekiq_scheduler: true)
- end
-
- context 'when Lua script is not yet loaded' do
- before do
- Gitlab::Redis::Queues.with { |redis| redis.script(:flush) }
- end
-
- it_behaves_like 'finds jobs that are due and enqueues them'
- end
-
- context 'when Lua script is already loaded' do
- before do
- Gitlab::SidekiqEnq.new.enqueue_jobs
- end
-
- it_behaves_like 'finds jobs that are due and enqueues them'
- end
- end
-end
diff --git a/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb
new file mode 100644
index 00000000000..534267a329e
--- /dev/null
+++ b/spec/lib/sidebars/projects/menus/shimo_menu_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Sidebars::Projects::Menus::ShimoMenu do
+ let_it_be_with_reload(:project) { create(:project) }
+
+ let(:context) { Sidebars::Projects::Context.new(current_user: project.owner, container: project) }
+
+ subject(:shimo_menu) { described_class.new(context) }
+
+ describe '#render?' do
+ context 'without a valid Shimo integration' do
+ it "doesn't render the menu" do
+ expect(shimo_menu.render?).to be_falsey
+ end
+ end
+
+ context 'with a valid Shimo integration' do
+ let_it_be_with_reload(:shimo_integration) { create(:shimo_integration, project: project) }
+
+ context 'when integration is active' do
+ it 'renders the menu' do
+ expect(shimo_menu.render?).to eq true
+ end
+
+ it 'renders menu link' do
+ expected_url = Rails.application.routes.url_helpers.project_integrations_shimo_path(project)
+ expect(shimo_menu.link).to eq expected_url
+ end
+ end
+
+ context 'when integration is inactive' do
+ before do
+ shimo_integration.update!(active: false)
+ end
+
+ it "doesn't render the menu" do
+ expect(shimo_menu.render?).to eq false
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/integrations/shimo_spec.rb b/spec/models/integrations/shimo_spec.rb
index 25df8d2b249..41f3f3c0c16 100644
--- a/spec/models/integrations/shimo_spec.rb
+++ b/spec/models/integrations/shimo_spec.rb
@@ -38,4 +38,26 @@ RSpec.describe ::Integrations::Shimo do
end
end
end
+
+ describe 'Caching has_shimo on project_settings' do
+ let(:project) { create(:project) }
+
+ subject { project.project_setting.has_shimo? }
+
+ it 'sets the property to true when integration is active' do
+ create(:shimo_integration, project: project, active: true)
+
+ is_expected.to be(true)
+ end
+
+ it 'sets the property to false when integration is not active' do
+ create(:shimo_integration, project: project, active: false)
+
+ is_expected.to be(false)
+ end
+
+ it 'creates a project_setting record if one was not already created' do
+ expect { create(:shimo_integration) }.to change(ProjectSetting, :count).by(1)
+ end
+ end
end
diff --git a/spec/requests/projects/integrations/shimos_controller_spec.rb b/spec/requests/projects/integrations/shimos_controller_spec.rb
new file mode 100644
index 00000000000..7322143f87e
--- /dev/null
+++ b/spec/requests/projects/integrations/shimos_controller_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Projects::Integrations::ShimosController do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+ let_it_be(:shimo_integration) { create(:shimo_integration, project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #show' do
+ context 'when Shimo integration is inactive' do
+ before do
+ shimo_integration.update!(active: false)
+ end
+
+ it 'returns 404 status' do
+ get project_integrations_shimo_path(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when Shimo integration is active' do
+ it 'renders the "show" template' do
+ get project_integrations_shimo_path(project)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ expect(response.body).to include shimo_integration.external_wiki_url
+ end
+ end
+ end
+end