Commit 423c5f60 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '322755-add-email-issue-to-project' into 'master'

Add email issue to project modal to issues page refactor

See merge request gitlab-org/gitlab!60430
parents feda19e6 8a2b26e6
...@@ -12,6 +12,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -12,6 +12,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { toNumber } from 'lodash'; import { toNumber } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
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 IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants'; import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import { import {
...@@ -54,6 +55,7 @@ export default { ...@@ -54,6 +55,7 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
GlSprintf, GlSprintf,
IssuableByEmail,
IssuableList, IssuableList,
IssueCardTimeInfo, IssueCardTimeInfo,
BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'), BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'),
...@@ -86,6 +88,9 @@ export default { ...@@ -86,6 +88,9 @@ export default {
hasIssues: { hasIssues: {
default: false, default: false,
}, },
initialEmail: {
default: '',
},
isSignedIn: { isSignedIn: {
default: false, default: false,
}, },
...@@ -376,143 +381,146 @@ export default { ...@@ -376,143 +381,146 @@ export default {
</script> </script>
<template> <template>
<issuable-list <div v-if="hasIssues">
v-if="hasIssues" <issuable-list
:namespace="projectPath" :namespace="projectPath"
recent-searches-storage-key="issues" recent-searches-storage-key="issues"
:search-input-placeholder="__('Search or filter results…')" :search-input-placeholder="__('Search or filter results…')"
:search-tokens="searchTokens" :search-tokens="searchTokens"
:initial-filter-value="filterTokens" :initial-filter-value="filterTokens"
:sort-options="$options.sortOptions" :sort-options="$options.sortOptions"
:initial-sort-by="sortKey" :initial-sort-by="sortKey"
:issuables="issues" :issuables="issues"
:tabs="$options.IssuableListTabs" :tabs="$options.IssuableListTabs"
:current-tab="state" :current-tab="state"
:tab-counts="tabCounts" :tab-counts="tabCounts"
:issuables-loading="isLoading" :issuables-loading="isLoading"
:is-manual-ordering="isManualOrdering" :is-manual-ordering="isManualOrdering"
:show-bulk-edit-sidebar="showBulkEditSidebar" :show-bulk-edit-sidebar="showBulkEditSidebar"
:show-pagination-controls="showPaginationControls" :show-pagination-controls="showPaginationControls"
:total-items="totalIssues" :total-items="totalIssues"
:current-page="page" :current-page="page"
:previous-page="page - 1" :previous-page="page - 1"
:next-page="page + 1" :next-page="page + 1"
:url-params="urlParams" :url-params="urlParams"
@click-tab="handleClickTab" @click-tab="handleClickTab"
@filter="handleFilter" @filter="handleFilter"
@page-change="handlePageChange" @page-change="handlePageChange"
@reorder="handleReorder" @reorder="handleReorder"
@sort="handleSort" @sort="handleSort"
@update-legacy-bulk-edit="handleUpdateLegacyBulkEdit" @update-legacy-bulk-edit="handleUpdateLegacyBulkEdit"
> >
<template #nav-actions> <template #nav-actions>
<gl-button <gl-button
v-gl-tooltip v-gl-tooltip
:href="rssPath" :href="rssPath"
icon="rss" icon="rss"
:title="$options.i18n.rssLabel" :title="$options.i18n.rssLabel"
:aria-label="$options.i18n.rssLabel" :aria-label="$options.i18n.rssLabel"
/> />
<gl-button <gl-button
v-gl-tooltip v-gl-tooltip
:href="calendarPath" :href="calendarPath"
icon="calendar" icon="calendar"
:title="$options.i18n.calendarLabel" :title="$options.i18n.calendarLabel"
:aria-label="$options.i18n.calendarLabel" :aria-label="$options.i18n.calendarLabel"
/> />
<csv-import-export-buttons <csv-import-export-buttons
class="gl-mr-3" class="gl-mr-3"
:export-csv-path="exportCsvPathWithQuery" :export-csv-path="exportCsvPathWithQuery"
:issuable-count="totalIssues" :issuable-count="totalIssues"
/> />
<gl-button <gl-button
v-if="canBulkUpdate" v-if="canBulkUpdate"
:disabled="showBulkEditSidebar" :disabled="showBulkEditSidebar"
@click="handleBulkUpdateClick" @click="handleBulkUpdateClick"
> >
{{ __('Edit issues') }} {{ __('Edit issues') }}
</gl-button> </gl-button>
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm"> <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }} {{ $options.i18n.newIssueLabel }}
</gl-button> </gl-button>
</template> </template>
<template #timeframe="{ issuable = {} }"> <template #timeframe="{ issuable = {} }">
<issue-card-time-info :issue="issuable" /> <issue-card-time-info :issue="issuable" />
</template> </template>
<template #statistics="{ issuable = {} }">
<li
v-if="issuable.mergeRequestsCount"
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="__('Related merge requests')"
data-testid="issuable-mr"
>
<gl-icon name="merge-request" />
{{ issuable.mergeRequestsCount }}
</li>
<li
v-if="issuable.upvotes"
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="__('Upvotes')"
data-testid="issuable-upvotes"
>
<gl-icon name="thumb-up" />
{{ issuable.upvotes }}
</li>
<li
v-if="issuable.downvotes"
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="__('Downvotes')"
data-testid="issuable-downvotes"
>
<gl-icon name="thumb-down" />
{{ issuable.downvotes }}
</li>
<blocking-issues-count
class="gl-display-none gl-sm-display-block"
:blocking-issues-count="issuable.blockingIssuesCount"
:is-list-item="true"
/>
</template>
<template #statistics="{ issuable = {} }"> <template #empty-state>
<li <gl-empty-state
v-if="issuable.mergeRequestsCount" v-if="searchQuery"
v-gl-tooltip :description="$options.i18n.noSearchResultsDescription"
class="gl-display-none gl-sm-display-block" :title="$options.i18n.noSearchResultsTitle"
:title="__('Related merge requests')" :svg-path="emptyStateSvgPath"
data-testid="issuable-mr" >
> <template #actions>
<gl-icon name="merge-request" /> <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ issuable.mergeRequestsCount }} {{ $options.i18n.newIssueLabel }}
</li> </gl-button>
<li </template>
v-if="issuable.upvotes" </gl-empty-state>
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="__('Upvotes')"
data-testid="issuable-upvotes"
>
<gl-icon name="thumb-up" />
{{ issuable.upvotes }}
</li>
<li
v-if="issuable.downvotes"
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="__('Downvotes')"
data-testid="issuable-downvotes"
>
<gl-icon name="thumb-down" />
{{ issuable.downvotes }}
</li>
<blocking-issues-count
class="gl-display-none gl-sm-display-block"
:blocking-issues-count="issuable.blockingIssuesCount"
:is-list-item="true"
/>
</template>
<template #empty-state> <gl-empty-state
<gl-empty-state v-else-if="isOpenTab"
v-if="searchQuery" :description="$options.i18n.noOpenIssuesDescription"
:description="$options.i18n.noSearchResultsDescription" :title="$options.i18n.noOpenIssuesTitle"
:title="$options.i18n.noSearchResultsTitle" :svg-path="emptyStateSvgPath"
:svg-path="emptyStateSvgPath" >
> <template #actions>
<template #actions> <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm"> {{ $options.i18n.newIssueLabel }}
{{ $options.i18n.newIssueLabel }} </gl-button>
</gl-button> </template>
</template> </gl-empty-state>
</gl-empty-state>
<gl-empty-state <gl-empty-state
v-else-if="isOpenTab" v-else
:description="$options.i18n.noOpenIssuesDescription" :title="$options.i18n.noClosedIssuesTitle"
:title="$options.i18n.noOpenIssuesTitle" :svg-path="emptyStateSvgPath"
:svg-path="emptyStateSvgPath" />
> </template>
<template #actions> </issuable-list>
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }}
</gl-button>
</template>
</gl-empty-state>
<gl-empty-state <issuable-by-email v-if="initialEmail" class="gl-text-center gl-pt-5 gl-pb-7" />
v-else </div>
:title="$options.i18n.noClosedIssuesTitle"
:svg-path="emptyStateSvgPath"
/>
</template>
</issuable-list>
<div v-else-if="isSignedIn"> <div v-else-if="isSignedIn">
<gl-empty-state <gl-empty-state
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { IssuableType } from '~/issue_show/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue'; import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
...@@ -80,6 +81,7 @@ export function initIssuesListApp() { ...@@ -80,6 +81,7 @@ export function initIssuesListApp() {
canEdit, canEdit,
canImportIssues, canImportIssues,
email, email,
emailsHelpPagePath,
emptyStateSvgPath, emptyStateSvgPath,
endpoint, endpoint,
exportCsvPath, exportCsvPath,
...@@ -88,15 +90,19 @@ export function initIssuesListApp() { ...@@ -88,15 +90,19 @@ export function initIssuesListApp() {
hasIssues, hasIssues,
hasIssueWeightsFeature, hasIssueWeightsFeature,
importCsvIssuesPath, importCsvIssuesPath,
initialEmail,
isSignedIn, isSignedIn,
issuesPath, issuesPath,
jiraIntegrationPath, jiraIntegrationPath,
markdownHelpPath,
maxAttachmentSize, maxAttachmentSize,
newIssuePath, newIssuePath,
projectImportJiraPath, projectImportJiraPath,
projectLabelsPath, projectLabelsPath,
projectMilestonesPath, projectMilestonesPath,
projectPath, projectPath,
quickActionsHelpPath,
resetPath,
rssPath, rssPath,
showNewIssueLink, showNewIssueLink,
signInPath, signInPath,
...@@ -138,6 +144,13 @@ export function initIssuesListApp() { ...@@ -138,6 +144,13 @@ export function initIssuesListApp() {
showExportButton: parseBoolean(hasIssues), showExportButton: parseBoolean(hasIssues),
showImportButton: parseBoolean(canImportIssues), showImportButton: parseBoolean(canImportIssues),
showLabel: !parseBoolean(hasIssues), showLabel: !parseBoolean(hasIssues),
// For IssuableByEmail component
emailsHelpPagePath,
initialEmail,
issuableType: IssuableType.Issue,
markdownHelpPath,
quickActionsHelpPath,
resetPath,
}, },
render: (createComponent) => createComponent(IssuesListApp), render: (createComponent) => createComponent(IssuesListApp),
}); });
......
...@@ -1032,11 +1032,6 @@ pre.light-well { ...@@ -1032,11 +1032,6 @@ pre.light-well {
} }
} }
.issuable-footer {
padding-top: $gl-padding;
padding-bottom: 37px;
}
.project-ci-linter { .project-ci-linter {
.ci-editor { .ci-editor {
height: 400px; height: 400px;
......
...@@ -172,20 +172,25 @@ module IssuesHelper ...@@ -172,20 +172,25 @@ module IssuesHelper
can_edit: can?(current_user, :admin_project, project).to_s, can_edit: can?(current_user, :admin_project, project).to_s,
can_import_issues: can?(current_user, :import_issues, @project).to_s, can_import_issues: can?(current_user, :import_issues, @project).to_s,
email: current_user&.notification_email, email: current_user&.notification_email,
emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'),
empty_state_svg_path: image_path('illustrations/issues.svg'), empty_state_svg_path: image_path('illustrations/issues.svg'),
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project), export_csv_path: export_csv_project_issues_path(project),
has_issues: project_issues(project).exists?.to_s, has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: import_csv_namespace_project_issues_path, import_csv_issues_path: import_csv_namespace_project_issues_path,
initial_email: project.new_issuable_address(current_user, 'issue'),
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
issues_path: project_issues_path(project), issues_path: project_issues_path(project),
jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'),
markdown_help_path: help_page_path('user/markdown'),
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json), project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path, project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: url_for(safe_params.merge(rss_url_options)), rss_path: url_for(safe_params.merge(rss_url_options)),
show_new_issue_link: show_new_issue_link?(project).to_s, show_new_issue_link: show_new_issue_link?(project).to_s,
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
.issues-holder .issues-holder
= render 'issues' = render 'issues'
- if new_issue_email - if new_issue_email
.issuable-footer.text-center .gl-text-center.gl-pt-5.gl-pb-7
.js-issueable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } } .js-issueable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else - else
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project) - new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
- if new_merge_request_email - if new_merge_request_email
.issuable-footer.text-center .gl-text-center.gl-pt-5.gl-pb-7
.js-issueable-by-email{ data: { initial_email: new_merge_request_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } } .js-issueable-by-email{ data: { initial_email: new_merge_request_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else - else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { apiParams, filteredTokens, locationSearch, urlParams } from 'jest/issues_list/mock_data'; import { apiParams, filteredTokens, locationSearch, urlParams } from 'jest/issues_list/mock_data';
import createFlash from '~/flash'; import createFlash from '~/flash';
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 IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants'; import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue'; import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
...@@ -24,7 +25,6 @@ import { setUrlParams } from '~/lib/utils/url_utility'; ...@@ -24,7 +25,6 @@ import { setUrlParams } from '~/lib/utils/url_utility';
jest.mock('~/flash'); jest.mock('~/flash');
describe('IssuesListApp component', () => { describe('IssuesListApp component', () => {
const originalWindowLocation = window.location;
let axiosMock; let axiosMock;
let wrapper; let wrapper;
...@@ -65,6 +65,7 @@ describe('IssuesListApp component', () => { ...@@ -65,6 +65,7 @@ describe('IssuesListApp component', () => {
}; };
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons); const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
const findIssuableByEmail = () => wrapper.findComponent(IssuableByEmail);
const findGlButton = () => wrapper.findComponent(GlButton); const findGlButton = () => wrapper.findComponent(GlButton);
const findGlButtons = () => wrapper.findAllComponents(GlButton); const findGlButtons = () => wrapper.findAllComponents(GlButton);
const findGlButtonAt = (index) => findGlButtons().at(index); const findGlButtonAt = (index) => findGlButtons().at(index);
...@@ -88,7 +89,7 @@ describe('IssuesListApp component', () => { ...@@ -88,7 +89,7 @@ describe('IssuesListApp component', () => {
}); });
afterEach(() => { afterEach(() => {
window.location = originalWindowLocation; global.jsdom.reconfigure({ url: TEST_HOST });
axiosMock.reset(); axiosMock.reset();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -122,34 +123,31 @@ describe('IssuesListApp component', () => { ...@@ -122,34 +123,31 @@ describe('IssuesListApp component', () => {
describe('header action buttons', () => { describe('header action buttons', () => {
it('renders rss button', () => { it('renders rss button', () => {
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
expect(findGlButtonAt(0).props('icon')).toBe('rss');
expect(findGlButtonAt(0).attributes()).toMatchObject({ expect(findGlButtonAt(0).attributes()).toMatchObject({
href: defaultProvide.rssPath, href: defaultProvide.rssPath,
icon: 'rss',
'aria-label': IssuesListApp.i18n.rssLabel, 'aria-label': IssuesListApp.i18n.rssLabel,
}); });
}); });
it('renders calendar button', () => { it('renders calendar button', () => {
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
expect(findGlButtonAt(1).props('icon')).toBe('calendar');
expect(findGlButtonAt(1).attributes()).toMatchObject({ expect(findGlButtonAt(1).attributes()).toMatchObject({
href: defaultProvide.calendarPath, href: defaultProvide.calendarPath,
icon: 'calendar',
'aria-label': IssuesListApp.i18n.calendarLabel, 'aria-label': IssuesListApp.i18n.calendarLabel,
}); });
}); });
it('renders csv import/export component', async () => { it('renders csv import/export component', async () => {
const search = '?page=1&search=refactor'; const search = '?page=1&search=refactor&state=opened&order_by=created_at&sort=desc';
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${search}` });
writable: true,
value: { search },
});
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
await waitForPromises(); await waitForPromises();
...@@ -161,7 +159,7 @@ describe('IssuesListApp component', () => { ...@@ -161,7 +159,7 @@ describe('IssuesListApp component', () => {
describe('bulk edit button', () => { describe('bulk edit button', () => {
it('renders when user has permissions', () => { it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true } }); wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('Edit issues'); expect(findGlButtonAt(2).text()).toBe('Edit issues');
}); });
...@@ -173,7 +171,7 @@ describe('IssuesListApp component', () => { ...@@ -173,7 +171,7 @@ describe('IssuesListApp component', () => {
}); });
it('emits "issuables:enableBulkEdit" event to legacy bulk edit class', () => { it('emits "issuables:enableBulkEdit" event to legacy bulk edit class', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true } }); wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
jest.spyOn(eventHub, '$emit'); jest.spyOn(eventHub, '$emit');
...@@ -185,7 +183,7 @@ describe('IssuesListApp component', () => { ...@@ -185,7 +183,7 @@ describe('IssuesListApp component', () => {
describe('new issue button', () => { describe('new issue button', () => {
it('renders when user has permissions', () => { it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { showNewIssueLink: true } }); wrapper = mountComponent({ provide: { showNewIssueLink: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('New issue'); expect(findGlButtonAt(2).text()).toBe('New issue');
expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath); expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath);
...@@ -204,10 +202,7 @@ describe('IssuesListApp component', () => { ...@@ -204,10 +202,7 @@ describe('IssuesListApp component', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
const page = 5; const page = 5;
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams({ page }, TEST_HOST) });
writable: true,
value: { href: setUrlParams({ page }, TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -217,10 +212,7 @@ describe('IssuesListApp component', () => { ...@@ -217,10 +212,7 @@ describe('IssuesListApp component', () => {
describe('search', () => { describe('search', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` });
writable: true,
value: { search: locationSearch },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -230,10 +222,7 @@ describe('IssuesListApp component', () => { ...@@ -230,10 +222,7 @@ describe('IssuesListApp component', () => {
describe('sort', () => { describe('sort', () => {
it.each(Object.keys(sortParams))('is set as %s from the url params', (sortKey) => { it.each(Object.keys(sortParams))('is set as %s from the url params', (sortKey) => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams(sortParams[sortKey], TEST_HOST) });
writable: true,
value: { href: setUrlParams(sortParams[sortKey], TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -248,10 +237,7 @@ describe('IssuesListApp component', () => { ...@@ -248,10 +237,7 @@ describe('IssuesListApp component', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
const initialState = IssuableStates.All; const initialState = IssuableStates.All;
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams({ state: initialState }, TEST_HOST) });
writable: true,
value: { href: setUrlParams({ state: initialState }, TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -261,10 +247,7 @@ describe('IssuesListApp component', () => { ...@@ -261,10 +247,7 @@ describe('IssuesListApp component', () => {
describe('filter tokens', () => { describe('filter tokens', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` });
writable: true,
value: { search: locationSearch },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -290,16 +273,25 @@ describe('IssuesListApp component', () => { ...@@ -290,16 +273,25 @@ describe('IssuesListApp component', () => {
); );
}); });
describe('IssuableByEmail component', () => {
describe.each([true, false])(`when issue creation by email is enabled=%s`, (enabled) => {
it(`${enabled ? 'renders' : 'does not render'}`, () => {
wrapper = mountComponent({ provide: { initialEmail: enabled } });
expect(findIssuableByEmail().exists()).toBe(enabled);
});
});
});
describe('empty states', () => { describe('empty states', () => {
describe('when there are issues', () => { describe('when there are issues', () => {
describe('when search returns no results', () => { describe('when search returns no results', () => {
beforeEach(() => { beforeEach(async () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}?search=no+results` });
writable: true,
value: { search: '?search=no+results' }, wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
});
wrapper = mountComponent({ provide: { hasIssues: true } }); await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
...@@ -312,8 +304,10 @@ describe('IssuesListApp component', () => { ...@@ -312,8 +304,10 @@ describe('IssuesListApp component', () => {
}); });
describe('when "Open" tab has no issues', () => { describe('when "Open" tab has no issues', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = mountComponent({ provide: { hasIssues: true } }); wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
...@@ -327,12 +321,13 @@ describe('IssuesListApp component', () => { ...@@ -327,12 +321,13 @@ describe('IssuesListApp component', () => {
describe('when "Closed" tab has no issues', () => { describe('when "Closed" tab has no issues', () => {
beforeEach(async () => { beforeEach(async () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({
writable: true, url: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST),
value: { href: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST) },
}); });
wrapper = mountComponent({ provide: { hasIssues: true } }); wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
......
...@@ -300,20 +300,25 @@ RSpec.describe IssuesHelper do ...@@ -300,20 +300,25 @@ RSpec.describe IssuesHelper do
can_edit: 'true', can_edit: 'true',
can_import_issues: 'true', can_import_issues: 'true',
email: current_user&.notification_email, email: current_user&.notification_email,
emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'),
empty_state_svg_path: '#', empty_state_svg_path: '#',
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project), export_csv_path: export_csv_project_issues_path(project),
has_issues: project_issues(project).exists?.to_s, has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#', import_csv_issues_path: '#',
initial_email: project.new_issuable_address(current_user, 'issue'),
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
issues_path: project_issues_path(project), issues_path: project_issues_path(project),
jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'),
markdown_help_path: help_page_path('user/markdown'),
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json), project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path, project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: '#', rss_path: '#',
show_new_issue_link: 'true', show_new_issue_link: 'true',
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
......
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