Commit 9590b183 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '322755-add-new-issues-features' into 'master'

Add spam icon and disabled reordering alert to issues refactor

See merge request gitlab-org/gitlab!74711
parents d62e2dcd c85ff3fe
...@@ -185,6 +185,13 @@ export default { ...@@ -185,6 +185,13 @@ export default {
:title="__('Confidential')" :title="__('Confidential')"
:aria-label="__('Confidential')" :aria-label="__('Confidential')"
/> />
<gl-icon
v-if="issuable.hidden"
v-gl-tooltip
name="spam"
:title="__('This issue is hidden because its author has been banned')"
:aria-label="__('Hidden')"
/>
<gl-link class="issue-title-text" dir="auto" :href="webUrl" v-bind="issuableTitleProps"> <gl-link class="issue-title-text" dir="auto" :href="webUrl" v-bind="issuableTitleProps">
{{ issuable.title }} {{ issuable.title }}
<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" /> <gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
...@@ -202,7 +209,7 @@ export default { ...@@ -202,7 +209,7 @@ export default {
<span v-else data-testid="issuable-reference" class="issuable-reference"> <span v-else data-testid="issuable-reference" class="issuable-reference">
{{ reference }} {{ reference }}
</span> </span>
<span class="gl-display-none gl-sm-display-inline-block"> <span class="gl-display-none gl-sm-display-inline">
<span aria-hidden="true">&middot;</span> <span aria-hidden="true">&middot;</span>
<span class="issuable-authored gl-mr-3"> <span class="issuable-authored gl-mr-3">
<gl-sprintf :message="__('created %{timeAgo} by %{author}')"> <gl-sprintf :message="__('created %{timeAgo} by %{author}')">
......
...@@ -11,7 +11,7 @@ import { ...@@ -11,7 +11,7 @@ import {
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql'; import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql';
import createFlash from '~/flash'; import createFlash, { FLASH_TYPES } from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants'; import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { ITEM_TYPE } from '~/groups/constants'; import { ITEM_TYPE } from '~/groups/constants';
...@@ -157,6 +157,9 @@ export default { ...@@ -157,6 +157,9 @@ export default {
initialEmail: { initialEmail: {
default: '', default: '',
}, },
isIssueRepositioningDisabled: {
default: false,
},
isProject: { isProject: {
default: false, default: false,
}, },
...@@ -184,8 +187,13 @@ export default { ...@@ -184,8 +187,13 @@ export default {
}, },
data() { data() {
const state = getParameterByName(PARAM_STATE); const state = getParameterByName(PARAM_STATE);
const sortKey = getSortKey(getParameterByName(PARAM_SORT));
const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC; const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
let sortKey = getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey;
if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
this.showIssueRepositioningMessage();
sortKey = defaultSortKey;
}
return { return {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)), dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
...@@ -196,7 +204,7 @@ export default { ...@@ -196,7 +204,7 @@ export default {
pageInfo: {}, pageInfo: {},
pageParams: getInitialPageParams(sortKey), pageParams: getInitialPageParams(sortKey),
showBulkEditSidebar: false, showBulkEditSidebar: false,
sortKey: sortKey || defaultSortKey, sortKey,
state: state || IssuableStates.Opened, state: state || IssuableStates.Opened,
}; };
}, },
...@@ -611,11 +619,22 @@ export default { ...@@ -611,11 +619,22 @@ export default {
}); });
}, },
handleSort(sortKey) { handleSort(sortKey) {
if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
this.showIssueRepositioningMessage();
return;
}
if (this.sortKey !== sortKey) { if (this.sortKey !== sortKey) {
this.pageParams = getInitialPageParams(sortKey); this.pageParams = getInitialPageParams(sortKey);
} }
this.sortKey = sortKey; this.sortKey = sortKey;
}, },
showIssueRepositioningMessage() {
createFlash({
message: this.$options.i18n.issueRepositioningMessage,
type: FLASH_TYPES.NOTICE,
});
},
toggleBulkEditSidebar(showBulkEditSidebar) { toggleBulkEditSidebar(showBulkEditSidebar) {
this.showBulkEditSidebar = showBulkEditSidebar; this.showBulkEditSidebar = showBulkEditSidebar;
}, },
......
...@@ -75,6 +75,9 @@ export const i18n = { ...@@ -75,6 +75,9 @@ export const i18n = {
editIssues: __('Edit issues'), editIssues: __('Edit issues'),
errorFetchingCounts: __('An error occurred while getting issue counts'), errorFetchingCounts: __('An error occurred while getting issue counts'),
errorFetchingIssues: __('An error occurred while loading issues'), errorFetchingIssues: __('An error occurred while loading issues'),
issueRepositioningMessage: __(
'Issues are being rebalanced at the moment, so manual reordering is disabled.',
),
jiraIntegrationMessage: s__( jiraIntegrationMessage: s__(
'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.', 'JiraService|%{jiraDocsLinkStart}Enable the Jira integration%{jiraDocsLinkEnd} to view your Jira issues in GitLab.',
), ),
......
...@@ -129,6 +129,7 @@ export function mountIssuesListApp() { ...@@ -129,6 +129,7 @@ export function mountIssuesListApp() {
hasMultipleIssueAssigneesFeature, hasMultipleIssueAssigneesFeature,
importCsvIssuesPath, importCsvIssuesPath,
initialEmail, initialEmail,
isIssueRepositioningDisabled,
isProject, isProject,
isSignedIn, isSignedIn,
jiraIntegrationPath, jiraIntegrationPath,
...@@ -161,6 +162,7 @@ export function mountIssuesListApp() { ...@@ -161,6 +162,7 @@ export function mountIssuesListApp() {
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
hasIterationsFeature: parseBoolean(hasIterationsFeature), hasIterationsFeature: parseBoolean(hasIterationsFeature),
hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature),
isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled),
isProject: parseBoolean(isProject), isProject: parseBoolean(isProject),
isSignedIn: parseBoolean(isSignedIn), isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath, jiraIntegrationPath,
......
...@@ -6,6 +6,7 @@ fragment IssueFragment on Issue { ...@@ -6,6 +6,7 @@ fragment IssueFragment on Issue {
createdAt createdAt
downvotes downvotes
dueDate dueDate
hidden
humanTimeEstimate humanTimeEstimate
mergeRequestsCount mergeRequestsCount
moved moved
......
...@@ -212,6 +212,7 @@ module IssuesHelper ...@@ -212,6 +212,7 @@ module IssuesHelper
calendar_path: url_for(safe_params.merge(calendar_url_options)), calendar_path: url_for(safe_params.merge(calendar_url_options)),
empty_state_svg_path: image_path('illustrations/issues.svg'), empty_state_svg_path: image_path('illustrations/issues.svg'),
full_path: namespace.full_path, full_path: namespace.full_path,
is_issue_repositioning_disabled: issue_repositioning_disabled?.to_s,
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
rss_path: url_for(safe_params.merge(rss_url_options)), rss_path: url_for(safe_params.merge(rss_url_options)),
......
...@@ -17092,6 +17092,9 @@ msgstr "" ...@@ -17092,6 +17092,9 @@ msgstr ""
msgid "Hi %{username}!" msgid "Hi %{username}!"
msgstr "" msgstr ""
msgid "Hidden"
msgstr ""
msgid "Hide" msgid "Hide"
msgstr "" msgstr ""
......
import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui'; import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date'; 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 IssuableItem from '~/issuable_list/components/issuable_item.vue';
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data'; 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, { shallowMount(IssuableItem, {
propsData: { propsData: {
issuableSymbol, issuableSymbol,
issuable, issuable,
enableLabelPermalinks: true, enableLabelPermalinks,
showDiscussions: true, showDiscussions: true,
showCheckbox: false, showCheckbox,
}, },
slots, slots,
stubs: { stubs: {
...@@ -34,7 +40,6 @@ describe('IssuableItem', () => { ...@@ -34,7 +40,6 @@ describe('IssuableItem', () => {
beforeEach(() => { beforeEach(() => {
gon.gitlab_url = MOCK_GITLAB_URL; gon.gitlab_url = MOCK_GITLAB_URL;
wrapper = createComponent();
}); });
afterEach(() => { afterEach(() => {
...@@ -45,6 +50,8 @@ describe('IssuableItem', () => { ...@@ -45,6 +50,8 @@ describe('IssuableItem', () => {
describe('computed', () => { describe('computed', () => {
describe('author', () => { describe('author', () => {
it('returns `issuable.author` reference', () => { it('returns `issuable.author` reference', () => {
wrapper = createComponent();
expect(wrapper.vm.author).toEqual(mockIssuable.author); expect(wrapper.vm.author).toEqual(mockIssuable.author);
}); });
}); });
...@@ -59,7 +66,7 @@ describe('IssuableItem', () => { ...@@ -59,7 +66,7 @@ describe('IssuableItem', () => {
`( `(
'returns $returnValue when value of `issuable.author.id` is $authorId', 'returns $returnValue when value of `issuable.author.id` is $authorId',
async ({ authorId, returnValue }) => { async ({ authorId, returnValue }) => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
author: { author: {
...@@ -86,7 +93,7 @@ describe('IssuableItem', () => { ...@@ -86,7 +93,7 @@ describe('IssuableItem', () => {
`( `(
'returns $returnValue when `issuable.webUrl` is $urlType', 'returns $returnValue when `issuable.webUrl` is $urlType',
async ({ issuableWebUrl, returnValue }) => { async ({ issuableWebUrl, returnValue }) => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
webUrl: issuableWebUrl, webUrl: issuableWebUrl,
...@@ -102,11 +109,13 @@ describe('IssuableItem', () => { ...@@ -102,11 +109,13 @@ describe('IssuableItem', () => {
describe('labels', () => { describe('labels', () => {
it('returns `issuable.labels.nodes` reference when it is available', () => { it('returns `issuable.labels.nodes` reference when it is available', () => {
wrapper = createComponent();
expect(wrapper.vm.labels).toEqual(mockLabels); expect(wrapper.vm.labels).toEqual(mockLabels);
}); });
it('returns `issuable.labels` reference when it is available', async () => { it('returns `issuable.labels` reference when it is available', async () => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
labels: mockLabels, labels: mockLabels,
...@@ -119,7 +128,7 @@ describe('IssuableItem', () => { ...@@ -119,7 +128,7 @@ describe('IssuableItem', () => {
}); });
it('returns empty array when none of `issuable.labels.nodes` or `issuable.labels` are available', async () => { it('returns empty array when none of `issuable.labels.nodes` or `issuable.labels` are available', async () => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
labels: null, labels: null,
...@@ -134,12 +143,16 @@ describe('IssuableItem', () => { ...@@ -134,12 +143,16 @@ describe('IssuableItem', () => {
describe('assignees', () => { describe('assignees', () => {
it('returns `issuable.assignees` reference when it is available', () => { it('returns `issuable.assignees` reference when it is available', () => {
wrapper = createComponent();
expect(wrapper.vm.assignees).toBe(mockIssuable.assignees); expect(wrapper.vm.assignees).toBe(mockIssuable.assignees);
}); });
}); });
describe('updatedAt', () => { describe('updatedAt', () => {
it('returns string containing timeago string based on `issuable.updatedAt`', () => { it('returns string containing timeago string based on `issuable.updatedAt`', () => {
wrapper = createComponent();
expect(wrapper.vm.updatedAt).toContain('updated'); expect(wrapper.vm.updatedAt).toContain('updated');
expect(wrapper.vm.updatedAt).toContain('ago'); expect(wrapper.vm.updatedAt).toContain('ago');
}); });
...@@ -155,7 +168,7 @@ describe('IssuableItem', () => { ...@@ -155,7 +168,7 @@ describe('IssuableItem', () => {
`( `(
'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount', 'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount',
({ userDiscussionsCount, returnValue }) => { ({ userDiscussionsCount, returnValue }) => {
const wrapperWithDiscussions = createComponent({ wrapper = createComponent({
issuableSymbol: '#', issuableSymbol: '#',
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
...@@ -163,9 +176,7 @@ describe('IssuableItem', () => { ...@@ -163,9 +176,7 @@ describe('IssuableItem', () => {
}, },
}); });
expect(wrapperWithDiscussions.vm.showDiscussions).toBe(returnValue); expect(wrapper.findByTestId('issuable-discussions').exists()).toBe(returnValue);
wrapperWithDiscussions.destroy();
}, },
); );
}); });
...@@ -180,6 +191,8 @@ describe('IssuableItem', () => { ...@@ -180,6 +191,8 @@ describe('IssuableItem', () => {
`( `(
'return $returnValue when provided label param is a $labelType label', 'return $returnValue when provided label param is a $labelType label',
({ label, returnValue }) => { ({ label, returnValue }) => {
wrapper = createComponent();
expect(wrapper.vm.scopedLabel(label)).toBe(returnValue); expect(wrapper.vm.scopedLabel(label)).toBe(returnValue);
}, },
); );
...@@ -191,19 +204,23 @@ describe('IssuableItem', () => { ...@@ -191,19 +204,23 @@ describe('IssuableItem', () => {
${{ title: 'foo' }} | ${'title'} | ${'foo'} ${{ title: 'foo' }} | ${'title'} | ${'foo'}
${{ name: 'foo' }} | ${'name'} | ${'foo'} ${{ name: 'foo' }} | ${'name'} | ${'foo'}
`('returns string value of `label.$propWithTitle`', ({ label, returnValue }) => { `('returns string value of `label.$propWithTitle`', ({ label, returnValue }) => {
wrapper = createComponent();
expect(wrapper.vm.labelTitle(label)).toBe(returnValue); expect(wrapper.vm.labelTitle(label)).toBe(returnValue);
}); });
}); });
describe('labelTarget', () => { describe('labelTarget', () => {
it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => { it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => {
wrapper = createComponent();
expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe( expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe(
'?label_name[]=Documentation%20Update', '?label_name[]=Documentation%20Update',
); );
}); });
it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => { it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => {
wrapper.setProps({ wrapper = createComponent({
enableLabelPermalinks: false, enableLabelPermalinks: false,
}); });
...@@ -223,7 +240,7 @@ describe('IssuableItem', () => { ...@@ -223,7 +240,7 @@ describe('IssuableItem', () => {
`( `(
'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`', 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`',
async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => { async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
webUrl, webUrl,
...@@ -243,7 +260,7 @@ describe('IssuableItem', () => { ...@@ -243,7 +260,7 @@ describe('IssuableItem', () => {
); );
it('renders checkbox when `showCheckbox` prop is true', async () => { it('renders checkbox when `showCheckbox` prop is true', async () => {
wrapper.setProps({ wrapper = createComponent({
showCheckbox: true, showCheckbox: true,
}); });
...@@ -262,7 +279,7 @@ describe('IssuableItem', () => { ...@@ -262,7 +279,7 @@ describe('IssuableItem', () => {
}); });
it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => { it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
webUrl: 'http://jira.atlassian.net/browse/IG-1', webUrl: 'http://jira.atlassian.net/browse/IG-1',
...@@ -277,7 +294,7 @@ describe('IssuableItem', () => { ...@@ -277,7 +294,7 @@ describe('IssuableItem', () => {
}); });
it('renders issuable confidential icon when issuable is confidential', async () => { it('renders issuable confidential icon when issuable is confidential', async () => {
wrapper.setProps({ wrapper = createComponent({
issuable: { issuable: {
...mockIssuable, ...mockIssuable,
confidential: true, confidential: true,
...@@ -296,7 +313,21 @@ describe('IssuableItem', () => { ...@@ -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', () => { it('renders task status', () => {
wrapper = createComponent();
const taskStatus = wrapper.find('[data-testid="task-status"]'); const taskStatus = wrapper.find('[data-testid="task-status"]');
const expected = `${mockIssuable.taskCompletionStatus.completedCount} of ${mockIssuable.taskCompletionStatus.count} tasks completed`; const expected = `${mockIssuable.taskCompletionStatus.completedCount} of ${mockIssuable.taskCompletionStatus.count} tasks completed`;
...@@ -304,6 +335,8 @@ describe('IssuableItem', () => { ...@@ -304,6 +335,8 @@ describe('IssuableItem', () => {
}); });
it('renders issuable reference', () => { it('renders issuable reference', () => {
wrapper = createComponent();
const referenceEl = wrapper.find('[data-testid="issuable-reference"]'); const referenceEl = wrapper.find('[data-testid="issuable-reference"]');
expect(referenceEl.exists()).toBe(true); expect(referenceEl.exists()).toBe(true);
...@@ -311,7 +344,7 @@ describe('IssuableItem', () => { ...@@ -311,7 +344,7 @@ describe('IssuableItem', () => {
}); });
it('renders issuable reference via slot', () => { it('renders issuable reference via slot', () => {
const wrapperWithRefSlot = createComponent({ wrapper = createComponent({
issuableSymbol: '#', issuableSymbol: '#',
issuable: mockIssuable, issuable: mockIssuable,
slots: { slots: {
...@@ -320,15 +353,15 @@ describe('IssuableItem', () => { ...@@ -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.exists()).toBe(true);
expect(referenceEl.text()).toBe(`${mockIssuable.iid}`); expect(referenceEl.text()).toBe(`${mockIssuable.iid}`);
wrapperWithRefSlot.destroy();
}); });
it('renders issuable createdAt info', () => { it('renders issuable createdAt info', () => {
wrapper = createComponent();
const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]'); const createdAtEl = wrapper.find('[data-testid="issuable-created-at"]');
expect(createdAtEl.exists()).toBe(true); expect(createdAtEl.exists()).toBe(true);
...@@ -337,6 +370,8 @@ describe('IssuableItem', () => { ...@@ -337,6 +370,8 @@ describe('IssuableItem', () => {
}); });
it('renders issuable author info', () => { it('renders issuable author info', () => {
wrapper = createComponent();
const authorEl = wrapper.find('[data-testid="issuable-author"]'); const authorEl = wrapper.find('[data-testid="issuable-author"]');
expect(authorEl.exists()).toBe(true); expect(authorEl.exists()).toBe(true);
...@@ -351,7 +386,7 @@ describe('IssuableItem', () => { ...@@ -351,7 +386,7 @@ describe('IssuableItem', () => {
}); });
it('renders issuable author info via slot', () => { it('renders issuable author info via slot', () => {
const wrapperWithAuthorSlot = createComponent({ wrapper = createComponent({
issuableSymbol: '#', issuableSymbol: '#',
issuable: mockIssuable, issuable: mockIssuable,
slots: { slots: {
...@@ -360,16 +395,14 @@ describe('IssuableItem', () => { ...@@ -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.exists()).toBe(true);
expect(authorEl.text()).toBe(mockAuthor.name); expect(authorEl.text()).toBe(mockAuthor.name);
wrapperWithAuthorSlot.destroy();
}); });
it('renders timeframe via slot', () => { it('renders timeframe via slot', () => {
const wrapperWithTimeframeSlot = createComponent({ wrapper = createComponent({
issuableSymbol: '#', issuableSymbol: '#',
issuable: mockIssuable, issuable: mockIssuable,
slots: { slots: {
...@@ -378,15 +411,15 @@ describe('IssuableItem', () => { ...@@ -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.exists()).toBe(true);
expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020'); expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020');
wrapperWithTimeframeSlot.destroy();
}); });
it('renders gl-label component for each label present within `issuable` prop', () => { it('renders gl-label component for each label present within `issuable` prop', () => {
wrapper = createComponent();
const labelsEl = wrapper.findAll(GlLabel); const labelsEl = wrapper.findAll(GlLabel);
expect(labelsEl.exists()).toBe(true); expect(labelsEl.exists()).toBe(true);
...@@ -402,7 +435,7 @@ describe('IssuableItem', () => { ...@@ -402,7 +435,7 @@ describe('IssuableItem', () => {
}); });
it('renders issuable status via slot', () => { it('renders issuable status via slot', () => {
const wrapperWithStatusSlot = createComponent({ wrapper = createComponent({
issuableSymbol: '#', issuableSymbol: '#',
issuable: mockIssuable, issuable: mockIssuable,
slots: { slots: {
...@@ -411,15 +444,15 @@ describe('IssuableItem', () => { ...@@ -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.exists()).toBe(true);
expect(statusEl.text()).toBe(`${mockIssuable.state}`); expect(statusEl.text()).toBe(`${mockIssuable.state}`);
wrapperWithStatusSlot.destroy();
}); });
it('renders discussions count', () => { it('renders discussions count', () => {
wrapper = createComponent();
const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]'); const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]');
expect(discussionsEl.exists()).toBe(true); expect(discussionsEl.exists()).toBe(true);
...@@ -432,6 +465,8 @@ describe('IssuableItem', () => { ...@@ -432,6 +465,8 @@ describe('IssuableItem', () => {
}); });
it('renders issuable-assignees component', () => { it('renders issuable-assignees component', () => {
wrapper = createComponent();
const assigneesEl = wrapper.find(IssuableAssignees); const assigneesEl = wrapper.find(IssuableAssignees);
expect(assigneesEl.exists()).toBe(true); expect(assigneesEl.exists()).toBe(true);
...@@ -443,6 +478,8 @@ describe('IssuableItem', () => { ...@@ -443,6 +478,8 @@ describe('IssuableItem', () => {
}); });
it('renders issuable updatedAt info', () => { it('renders issuable updatedAt info', () => {
wrapper = createComponent();
const updatedAtEl = wrapper.find('[data-testid="issuable-updated-at"]'); const updatedAtEl = wrapper.find('[data-testid="issuable-updated-at"]');
expect(updatedAtEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC'); expect(updatedAtEl.attributes('title')).toBe('Sep 10, 2020 11:41am UTC');
......
...@@ -17,7 +17,7 @@ import { ...@@ -17,7 +17,7 @@ import {
locationSearch, locationSearch,
urlParams, urlParams,
} from 'jest/issues_list/mock_data'; } from 'jest/issues_list/mock_data';
import createFlash from '~/flash'; import createFlash, { FLASH_TYPES } from '~/flash';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
...@@ -29,6 +29,8 @@ import { ...@@ -29,6 +29,8 @@ import {
CREATED_DESC, CREATED_DESC,
DUE_DATE_OVERDUE, DUE_DATE_OVERDUE,
PARAM_DUE_DATE, PARAM_DUE_DATE,
RELATIVE_POSITION,
RELATIVE_POSITION_ASC,
TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_AUTHOR, TOKEN_TYPE_AUTHOR,
TOKEN_TYPE_CONFIDENTIAL, TOKEN_TYPE_CONFIDENTIAL,
...@@ -314,6 +316,29 @@ describe('IssuesListApp component', () => { ...@@ -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', () => { describe('state', () => {
...@@ -762,6 +787,30 @@ describe('IssuesListApp component', () => { ...@@ -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', () => { describe('when "update-legacy-bulk-edit" event is emitted by IssuableList', () => {
......
...@@ -22,6 +22,7 @@ export const getIssuesQueryResponse = { ...@@ -22,6 +22,7 @@ export const getIssuesQueryResponse = {
createdAt: '2021-05-22T04:08:01Z', createdAt: '2021-05-22T04:08:01Z',
downvotes: 2, downvotes: 2,
dueDate: '2021-05-29', dueDate: '2021-05-29',
hidden: false,
humanTimeEstimate: null, humanTimeEstimate: null,
mergeRequestsCount: false, mergeRequestsCount: false,
moved: false, moved: false,
......
...@@ -302,6 +302,7 @@ RSpec.describe IssuesHelper do ...@@ -302,6 +302,7 @@ RSpec.describe IssuesHelper do
allow(helper).to receive(:can?).and_return(true) allow(helper).to receive(:can?).and_return(true)
allow(helper).to receive(:image_path).and_return('#') allow(helper).to receive(:image_path).and_return('#')
allow(helper).to receive(:import_csv_namespace_project_issues_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('#') allow(helper).to receive(:url_for).and_return('#')
expected = { expected = {
...@@ -318,6 +319,7 @@ RSpec.describe IssuesHelper do ...@@ -318,6 +319,7 @@ RSpec.describe IssuesHelper do
has_any_issues: project_issues(project).exists?.to_s, has_any_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#', import_csv_issues_path: '#',
initial_email: project.new_issuable_address(current_user, 'issue'), initial_email: project.new_issuable_address(current_user, 'issue'),
is_issue_repositioning_disabled: 'true',
is_project: 'true', is_project: 'true',
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment