Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
855bf0533b
commit
be59dd1d43
39 changed files with 792 additions and 1583 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,6 +7,7 @@
|
|||
.bundle
|
||||
.chef
|
||||
.directory
|
||||
.eslintcache
|
||||
/.envrc
|
||||
eslint-report.html
|
||||
/.gitlab_shell_secret
|
||||
|
|
|
@ -1,5 +1,101 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
## 12.6.0
|
||||
|
||||
### Fixed (32 changes, 5 of them are from the community)
|
||||
|
||||
- Exclude forks from Group Security Dashboard filter. !14667
|
||||
- Clarify why Service Desk feature is unavailable. !19244
|
||||
- Bump code quality version in template to 0.85.5. !19354
|
||||
- Nullify user roles that have been accidentaly set to a value of 0. !19569
|
||||
- Display CI Minutes warning only if minutes left is still below last level. !19751
|
||||
- Add a unique constraint to `software_licenses.name` column. !19840
|
||||
- Link user accounts to new Smartcards identities on login. !20059
|
||||
- Allow valid namespace paths with dots for api PUT. !20079
|
||||
- Map software license names from the v1 license scan report to an equivalent SPDX identifer. !20195
|
||||
- Prefer sending external pull request pipeline statuses over general statuses to GitHub. !20364
|
||||
- Abort rendering of security reports that aren't enabled. !20381
|
||||
- Fix Infinite Scrolling on Environments Dashboard Project Selector. !20408
|
||||
- Link user accounts to new Smartcards certificate ldap identities on login. !20470
|
||||
- Handle design repositories when moving a project to a new storage. !20509
|
||||
- Resolve Version dropdown goes wrong if versions are not monotonic. !20515 (Tom Quirk)
|
||||
- Turn auto_complete_issues on by default. !20525
|
||||
- Handle design repositories when moving existing projects to Hashed Storage. !20540
|
||||
- Fix dependency metadata on the NPM registry responses. !20549
|
||||
- Fix the hiding of undismissed vulnerabilities. !20599
|
||||
- Fix check for existing ES limited indexing IDs. !20866
|
||||
- Show actions area for fixed vulnerabilities in merge requests. !20867
|
||||
- Fix typo in Kubernetes GKE setup error message. !21091
|
||||
- Include projects in subgroups in group boards relative position. !21189
|
||||
- Fix inability to add comments to a discussion in Design Management. !21229
|
||||
- Fix Infinity % / Infinity % on Stacked Progress Bar. !21437
|
||||
- Fix sort icon direction when sorting by weight. !21447 (Jan Beckmann)
|
||||
- Auto-focus title text box when creating new epics. !21516 (Jan Beckmann)
|
||||
- Fix analytics icon alignment. !21555
|
||||
- Invalid trial form to remember user & country. !21840
|
||||
- Fix styling on contribution analytics dashboard. !207012 (briankabiro)
|
||||
- Add correct link to milestone in groups for issuables list after refactor.
|
||||
- Show the proper message when adding a duplicate issue to an epic. (20175)
|
||||
|
||||
### Changed (13 changes, 1 of them is from the community)
|
||||
|
||||
- Make "Learn more about" links for security scanning popovers on merge request page open in new tab. !13333 (Daniel Tian)
|
||||
- Redirect Admin > Settings > Geo to Admin > Geo > Settings. !19833
|
||||
- Expose epic_id parameter in issues API. !19953
|
||||
- Allow to login with Smartcard certificates using SAN extensions that only defines one global email identity. !20052
|
||||
- Update SAST.gitlab-ci.yml - Add kubesec analyzer. !20129
|
||||
- Update start trial CTA in top right banner to only appear if all namespaces are free. !20177
|
||||
- Update billing page trial CTAs. !20383
|
||||
- Rename software_license_policies.approval_status to software_license_policies.classification. !20414
|
||||
- Add ability to edit Group Hooks. !20898
|
||||
- Improve the performance of group templates finder. !20947
|
||||
- Hide elasticsearch namespaces and projects when too many in rollout. !21225
|
||||
- Update Explore Geo Page. !21448
|
||||
- Renamed Conversational Development Index feature to DevOps Score.
|
||||
|
||||
### Performance (1 change)
|
||||
|
||||
- Do not trigger count query for pagination without count. !21232
|
||||
|
||||
### Added (24 changes, 2 of them are from the community)
|
||||
|
||||
- Add new approval rule type which allows anyone to approve. !15378
|
||||
- Add Personal access token expiry policy. !17344
|
||||
- Expose time logs for group issues via the GraphQL API. !18689
|
||||
- Add application settings needed for soft-deletion. !18790
|
||||
- Add link to new epic for promoted issues. !18839 (Jan Beckmann)
|
||||
- Use issue templates on service desk(backend). !19515
|
||||
- Log history for gitlab_subscriptions table. !19694
|
||||
- Resolve Show plan of root group on subgroup details page. !20218
|
||||
- Adjust group members API to include group SAML info. !20357
|
||||
- Add user ability to append template to incoming service desk issues. !20476
|
||||
- Add audit event when member access is removed due to expiration. !20529
|
||||
- Update CI templates to use sitespeed 11.2.0. !20561
|
||||
- Added migration for issue link types. !20617
|
||||
- Add security configuration navigation item. !20711
|
||||
- Create a new database composite index to support cross-project artifacts downloads. !20721
|
||||
- Add deployment API updated_at filters. !20731
|
||||
- Show loading spinner in design card while design is uploading. !20814
|
||||
- Add most affected projects to group security dashboard. !20892
|
||||
- Introduce Credentials Inventory. !20912
|
||||
- Add GraphQL mutation for changing weight of an issue. !21331
|
||||
- Cache vulnerability findings history endpoint for security dashboards. !21349
|
||||
- Added Marginalia feature which can generate PostgreSQL query comments to Gitlab. !21364 (BalaKumar)
|
||||
- Add API for states by country. !21417
|
||||
- Improved trials sign up for gitlab.com. !21650
|
||||
|
||||
### Other (8 changes, 2 of them are from the community)
|
||||
|
||||
- Store and look up design management version authorship from database. !17322
|
||||
- Remove redundant ManagedLicenses controller. !20131 (briankabiro)
|
||||
- Updated board_service.js to use boardStore directly. !20141 (nuwe1)
|
||||
- Delete any stale deploy access levels by group. !20689
|
||||
- Add project webhooks limits on GitLab.com. !20730
|
||||
- Remove the design_management_flag feature flag from the codebase. The feature flag toggles the Design Management feature, and has been enabled by default since 12.2. !20883
|
||||
- Remove operations_feature_flags_clients.token column. !21016
|
||||
- Update the alerts used in the Dependency List to follow GitLab design guidelines. !21760
|
||||
|
||||
|
||||
## 12.5.5
|
||||
|
||||
- No changes.
|
||||
|
|
|
@ -153,7 +153,7 @@ export default {
|
|||
),
|
||||
{
|
||||
startLink:
|
||||
'<a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#role-create" target="_blank" rel="noopener noreferrer">',
|
||||
'<a href="https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html#create-service-role" target="_blank" rel="noopener noreferrer">',
|
||||
externalLinkIcon: this.externalLinkIcon,
|
||||
endLink: '</a>',
|
||||
},
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { mapActions } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
FileIcon,
|
||||
ChangedFileIcon,
|
||||
},
|
||||
|
@ -17,7 +18,13 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
activeButtonText() {
|
||||
discardModalId() {
|
||||
return `discard-file-${this.activeFile.path}`;
|
||||
},
|
||||
discardModalTitle() {
|
||||
return sprintf(__('Discard changes to %{path}?'), { path: this.activeFile.path });
|
||||
},
|
||||
actionButtonText() {
|
||||
return this.activeFile.staged ? __('Unstage') : __('Stage');
|
||||
},
|
||||
isStaged() {
|
||||
|
@ -25,7 +32,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['stageChange', 'unstageChange']),
|
||||
...mapActions(['stageChange', 'unstageChange', 'discardFileChanges']),
|
||||
actionButtonClicked() {
|
||||
if (this.activeFile.staged) {
|
||||
this.unstageChange(this.activeFile.path);
|
||||
|
@ -34,7 +41,7 @@ export default {
|
|||
}
|
||||
},
|
||||
showDiscardModal() {
|
||||
$(document.getElementById(`discard-file-${this.activeFile.path}`)).modal('show');
|
||||
this.$refs.discardModal.show();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -53,6 +60,7 @@ export default {
|
|||
<div class="ml-auto">
|
||||
<button
|
||||
v-if="!isStaged"
|
||||
ref="discardButton"
|
||||
type="button"
|
||||
class="btn btn-remove btn-inverted append-right-8"
|
||||
@click="showDiscardModal"
|
||||
|
@ -60,6 +68,7 @@ export default {
|
|||
{{ __('Discard') }}
|
||||
</button>
|
||||
<button
|
||||
ref="actionButton"
|
||||
:class="{
|
||||
'btn-success': !isStaged,
|
||||
'btn-warning': isStaged,
|
||||
|
@ -68,8 +77,19 @@ export default {
|
|||
class="btn btn-inverted"
|
||||
@click="actionButtonClicked"
|
||||
>
|
||||
{{ activeButtonText }}
|
||||
{{ actionButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
<gl-modal
|
||||
ref="discardModal"
|
||||
ok-variant="danger"
|
||||
cancel-variant="light"
|
||||
:ok-title="__('Discard changes')"
|
||||
:modal-id="discardModalId"
|
||||
:title="discardModalTitle"
|
||||
@ok="discardFileChanges(activeFile.path)"
|
||||
>
|
||||
{{ __("You will lose all changes you've made to this file. This action cannot be undone.") }}
|
||||
</gl-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -141,7 +141,7 @@ export const getMergeRequestVersions = (
|
|||
});
|
||||
|
||||
export const openMergeRequest = (
|
||||
{ dispatch, state },
|
||||
{ dispatch, state, getters },
|
||||
{ projectId, targetProjectId, mergeRequestId } = {},
|
||||
) =>
|
||||
dispatch('getMergeRequestData', {
|
||||
|
@ -152,17 +152,18 @@ export const openMergeRequest = (
|
|||
.then(mr => {
|
||||
dispatch('setCurrentBranchId', mr.source_branch);
|
||||
|
||||
// getFiles needs to be called after getting the branch data
|
||||
// since files are fetched using the last commit sha of the branch
|
||||
return dispatch('getBranchData', {
|
||||
projectId,
|
||||
branchId: mr.source_branch,
|
||||
}).then(() =>
|
||||
dispatch('getFiles', {
|
||||
}).then(() => {
|
||||
const branch = getters.findBranch(projectId, mr.source_branch);
|
||||
|
||||
return dispatch('getFiles', {
|
||||
projectId,
|
||||
branchId: mr.source_branch,
|
||||
}),
|
||||
);
|
||||
ref: branch.commit.id,
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() =>
|
||||
dispatch('getMergeRequestVersions', {
|
||||
|
|
|
@ -111,7 +111,7 @@ export const loadFile = ({ dispatch, state }, { basePath }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const loadBranch = ({ dispatch }, { projectId, branchId }) =>
|
||||
export const loadBranch = ({ dispatch, getters }, { projectId, branchId }) =>
|
||||
dispatch('getBranchData', {
|
||||
projectId,
|
||||
branchId,
|
||||
|
@ -121,9 +121,13 @@ export const loadBranch = ({ dispatch }, { projectId, branchId }) =>
|
|||
projectId,
|
||||
branchId,
|
||||
});
|
||||
|
||||
const branch = getters.findBranch(projectId, branchId);
|
||||
|
||||
return dispatch('getFiles', {
|
||||
projectId,
|
||||
branchId,
|
||||
ref: branch.commit.id,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
@ -46,19 +46,20 @@ export const setDirectoryData = ({ state, commit }, { projectId, branchId, treeL
|
|||
});
|
||||
};
|
||||
|
||||
export const getFiles = ({ state, commit, dispatch, getters }, { projectId, branchId } = {}) =>
|
||||
export const getFiles = ({ state, commit, dispatch }, payload = {}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const { projectId, branchId, ref = branchId } = payload;
|
||||
|
||||
if (
|
||||
!state.trees[`${projectId}/${branchId}`] ||
|
||||
(state.trees[`${projectId}/${branchId}`].tree &&
|
||||
state.trees[`${projectId}/${branchId}`].tree.length === 0)
|
||||
) {
|
||||
const selectedProject = state.projects[projectId];
|
||||
const selectedBranch = getters.findBranch(projectId, branchId);
|
||||
|
||||
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
|
||||
service
|
||||
.getFiles(selectedProject.web_url, selectedBranch.commit.id)
|
||||
.getFiles(selectedProject.web_url, ref)
|
||||
.then(({ data }) => {
|
||||
const { entries, treeList } = decorateFiles({
|
||||
data,
|
||||
|
@ -77,8 +78,8 @@ export const getFiles = ({ state, commit, dispatch, getters }, { projectId, bran
|
|||
.catch(e => {
|
||||
dispatch('setErrorMessage', {
|
||||
text: __('An error occurred whilst loading all the files.'),
|
||||
action: payload =>
|
||||
dispatch('getFiles', payload).then(() => dispatch('setErrorMessage', null)),
|
||||
action: actionPayload =>
|
||||
dispatch('getFiles', actionPayload).then(() => dispatch('setErrorMessage', null)),
|
||||
actionText: __('Please try again'),
|
||||
actionPayload: { projectId, branchId },
|
||||
});
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import Cookies from 'js-cookie';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import flash from './flash';
|
||||
import BlobForkSuggestion from './blob/blob_fork_suggestion';
|
||||
import initChangesDropdown from './init_changes_dropdown';
|
||||
import bp from './breakpoints';
|
||||
import {
|
||||
parseUrlPathname,
|
||||
handleLocationHash,
|
||||
|
@ -194,7 +194,7 @@ export default class MergeRequestTabs {
|
|||
if (!isInVueNoteablePage()) {
|
||||
this.loadDiff(href);
|
||||
}
|
||||
if (bp.getBreakpointSize() !== 'lg') {
|
||||
if (bp.getBreakpointSize() !== 'xl') {
|
||||
this.shrinkView();
|
||||
}
|
||||
this.expandViewContainer();
|
||||
|
|
|
@ -4,11 +4,13 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
|||
import ZenMode from '~/zen_mode';
|
||||
import '~/notes/index';
|
||||
import initIssueableApp from '~/issue_show';
|
||||
import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace';
|
||||
import initRelatedMergeRequestsApp from '~/related_merge_requests';
|
||||
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
|
||||
|
||||
export default function() {
|
||||
initIssueableApp();
|
||||
initSentryErrorStackTraceApp();
|
||||
initRelatedMergeRequestsApp();
|
||||
new Issue(); // eslint-disable-line no-new
|
||||
new ShortcutsIssuable(); // eslint-disable-line no-new
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'SentryErrorStackTrace',
|
||||
components: {
|
||||
Stacktrace,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
issueStackTracePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState('details', ['loadingStacktrace', 'stacktraceData']),
|
||||
...mapGetters('details', ['stacktrace']),
|
||||
},
|
||||
mounted() {
|
||||
this.startPollingStacktrace(this.issueStackTracePath);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('details', ['startPollingStacktrace']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :class="{ 'border-bottom-0': loadingStacktrace }" class="card card-slim mt-4 mb-0">
|
||||
<div class="card-header border-bottom-0">
|
||||
<h5 class="card-title my-1">{{ __('Stack trace') }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loadingStacktrace" class="card">
|
||||
<gl-loading-icon class="py-2" label="Fetching stack trace" :size="1" />
|
||||
</div>
|
||||
<stacktrace v-else :entries="stacktrace" />
|
||||
</div>
|
||||
</template>
|
22
app/assets/javascripts/sentry_error_stack_trace/index.js
Normal file
22
app/assets/javascripts/sentry_error_stack_trace/index.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import Vue from 'vue';
|
||||
import SentryErrorStackTrace from './components/sentry_error_stack_trace.vue';
|
||||
import store from '~/error_tracking/store';
|
||||
|
||||
export default function initSentryErrorStacktrace() {
|
||||
const sentryErrorStackTraceEl = document.querySelector('#js-sentry-error-stack-trace');
|
||||
if (sentryErrorStackTraceEl) {
|
||||
const { issueStackTracePath } = sentryErrorStackTraceEl.dataset;
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: sentryErrorStackTraceEl,
|
||||
components: {
|
||||
SentryErrorStackTrace,
|
||||
},
|
||||
store,
|
||||
render: createElement =>
|
||||
createElement('sentry-error-stack-trace', {
|
||||
props: { issueStackTracePath },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -30,11 +30,16 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
statusHtml() {
|
||||
if (!this.user.status) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.user.status.emoji && this.user.status.message_html) {
|
||||
return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message_html}`;
|
||||
} else if (this.user.status.message_html) {
|
||||
return this.user.status.message_html;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
nameIsLoading() {
|
||||
|
@ -97,7 +102,9 @@ export default {
|
|||
class="animation-container-small mb-1"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="user.status" class="mt-2"><span v-html="statusHtml"></span></div>
|
||||
<div v-if="statusHtml" class="js-user-status mt-2">
|
||||
<span v-html="statusHtml"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</gl-popover>
|
||||
|
|
|
@ -71,6 +71,9 @@
|
|||
|
||||
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
|
||||
|
||||
- if @issue.sentry_issue.present?
|
||||
#js-sentry-error-stack-trace{ data: error_details_data(@project, @issue.sentry_issue.sentry_issue_identifier) }
|
||||
|
||||
= render_if_exists 'projects/issues/related_issues'
|
||||
|
||||
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add stacktrace to issue created from the sentry error detail page
|
||||
merge_request: 21438
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/himkp-27242.yml
Normal file
5
changelogs/unreleased/himkp-27242.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Fix issue: Discard button in Web IDE does nothing'
|
||||
merge_request: 21902
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/polish-user-popover.yml
Normal file
5
changelogs/unreleased/polish-user-popover.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove extra whitespace in user popover
|
||||
merge_request: 19938
|
||||
author:
|
||||
type: fixed
|
|
@ -336,8 +336,9 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
|
|||
- **Kubernetes cluster name** - The name you wish to give the cluster.
|
||||
- **Environment scope** - The [associated environment](index.md#setting-the-environment-scope-premium) to this cluster.
|
||||
- **Kubernetes version** - The Kubernetes version to use. Currently the only version supported is 1.14.
|
||||
- **Role name** - Select the [IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)
|
||||
to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf.
|
||||
- **Role name** - Select the [IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html)
|
||||
to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. This IAM role is separate
|
||||
to the IAM role created above, you will need to create it if it does not yet exist.
|
||||
- **Region** - The [region](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html)
|
||||
in which the cluster will be created.
|
||||
- **Key pair name** - Select the [key pair](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
|
||||
|
|
|
@ -3256,6 +3256,9 @@ msgstr ""
|
|||
msgid "Checkout|Checkout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cherry-pick this commit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4432,9 +4435,6 @@ msgstr ""
|
|||
msgid "Code owners"
|
||||
msgstr ""
|
||||
|
||||
msgid "CodeAnalytics|Max files"
|
||||
msgstr ""
|
||||
|
||||
msgid "CodeOwner|Pattern"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9513,12 +9513,6 @@ msgstr ""
|
|||
msgid "Identifier"
|
||||
msgstr ""
|
||||
|
||||
msgid "Identify areas of the codebase associated with a lot of churn, which can indicate potential code hotspots."
|
||||
msgstr ""
|
||||
|
||||
msgid "Identify the most frequently changed files in your repository"
|
||||
msgstr ""
|
||||
|
||||
msgid "Identities"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
"check-dependencies": "scripts/frontend/check_dependencies.sh",
|
||||
"clean": "rm -rf public/assets tmp/cache/*-loader",
|
||||
"dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
|
||||
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
|
||||
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
|
||||
"eslint": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
|
||||
"eslint-fix": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
|
||||
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
|
||||
"file-coverage": "scripts/frontend/file_test_coverage.js",
|
||||
"prejest": "yarn check-dependencies",
|
||||
|
|
|
@ -7,24 +7,11 @@ describe 'Dropdown assignee', :js do
|
|||
|
||||
let!(:project) { create(:project) }
|
||||
let!(:user) { create(:user, name: 'administrator', username: 'root') }
|
||||
let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
|
||||
let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
|
||||
let(:filter_dropdown) { find("#{js_dropdown_assignee} .filter-dropdown") }
|
||||
|
||||
def dropdown_assignee_size
|
||||
filter_dropdown.all('.filter-dropdown-item').size
|
||||
end
|
||||
|
||||
def click_assignee(text)
|
||||
find('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item', text: text).click
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.add_maintainer(user_john)
|
||||
project.add_maintainer(user_jacob)
|
||||
sign_in(user)
|
||||
create(:issue, project: project)
|
||||
|
||||
|
@ -32,37 +19,10 @@ describe 'Dropdown assignee', :js do
|
|||
end
|
||||
|
||||
describe 'behavior' do
|
||||
it 'opens when the search bar has assignee:' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
end
|
||||
|
||||
it 'shows loading indicator when opened' do
|
||||
slow_requests do
|
||||
# We aren't using `input_filtered_search` because we want to see the loading indicator
|
||||
filtered_search.set('assignee:')
|
||||
|
||||
expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'hides loading indicator when loaded' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
|
||||
end
|
||||
|
||||
it 'loads all the assignees when opened' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(dropdown_assignee_size).to eq(4)
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 2)
|
||||
end
|
||||
|
||||
it 'shows current user at top of dropdown' do
|
||||
|
@ -72,109 +32,6 @@ describe 'Dropdown assignee', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'filtering' do
|
||||
before do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
|
||||
end
|
||||
|
||||
it 'filters by name' do
|
||||
input_filtered_search('jac', submit: false, extra_space: false)
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive name' do
|
||||
input_filtered_search('JAC', submit: false, extra_space: false)
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
|
||||
end
|
||||
|
||||
it 'filters by username with symbol' do
|
||||
input_filtered_search('@ott', submit: false, extra_space: false)
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive username with symbol' do
|
||||
input_filtered_search('@OTT', submit: false, extra_space: false)
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
|
||||
end
|
||||
|
||||
it 'filters by username without symbol' do
|
||||
input_filtered_search('ott', submit: false, extra_space: false)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive username without symbol' do
|
||||
input_filtered_search('OTT', submit: false, extra_space: false)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
|
||||
expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown' do
|
||||
before do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
end
|
||||
|
||||
it 'fills in the assignee username when the assignee has not been filtered' do
|
||||
click_assignee(user_jacob.name)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
expect_tokens([assignee_token(user_jacob.name)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the assignee username when the assignee has been filtered' do
|
||||
input_filtered_search('roo', submit: false, extra_space: false)
|
||||
click_assignee(user.name)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
expect_tokens([assignee_token(user.name)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `None`' do
|
||||
find('#js-dropdown-assignee .filter-dropdown-item', text: 'None').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
expect_tokens([assignee_token('None')])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `Any`' do
|
||||
find('#js-dropdown-assignee .filter-dropdown-item', text: 'Any').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
expect_tokens([assignee_token('Any')])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown without Ajax call' do
|
||||
before do
|
||||
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
|
||||
|
@ -186,59 +43,11 @@ describe 'Dropdown assignee', :js do
|
|||
end
|
||||
|
||||
it 'selects current user' do
|
||||
find('#js-dropdown-assignee .filter-dropdown-item', text: user.username).click
|
||||
find("#{js_dropdown_assignee} .filter-dropdown-item", text: user.username).click
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: false)
|
||||
expect_tokens([assignee_token(user.username)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'input has existing content' do
|
||||
it 'opens assignee dropdown with existing search term' do
|
||||
input_filtered_search('searchTerm assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown with existing author' do
|
||||
input_filtered_search('author:@user assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown with existing label' do
|
||||
input_filtered_search('label:~bug assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown with existing milestone' do
|
||||
input_filtered_search('milestone:%v1.0 assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown with existing my-reaction' do
|
||||
input_filtered_search('my-reaction:star assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(page).to have_css(js_dropdown_assignee, visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
initial_size = dropdown_assignee_size
|
||||
|
||||
expect(initial_size).to be > 0
|
||||
|
||||
new_user = create(:user)
|
||||
project.add_maintainer(new_user)
|
||||
find('.filtered-search-box .clear-search').click
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(dropdown_assignee_size).to eq(initial_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,32 +7,11 @@ describe 'Dropdown author', :js do
|
|||
|
||||
let!(:project) { create(:project) }
|
||||
let!(:user) { create(:user, name: 'administrator', username: 'root') }
|
||||
let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
|
||||
let!(:user_jacob) { create(:user, name: 'Jacob', username: 'ooter32') }
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_author) { '#js-dropdown-author' }
|
||||
|
||||
def send_keys_to_filtered_search(input)
|
||||
input.split("").each do |i|
|
||||
filtered_search.send_keys(i)
|
||||
end
|
||||
|
||||
sleep 0.5
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def dropdown_author_size
|
||||
page.all('#js-dropdown-author .filter-dropdown .filter-dropdown-item').size
|
||||
end
|
||||
|
||||
def click_author(text)
|
||||
find('#js-dropdown-author .filter-dropdown .filter-dropdown-item', text: text).click
|
||||
end
|
||||
let(:filter_dropdown) { find("#{js_dropdown_author} .filter-dropdown") }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
project.add_maintainer(user_john)
|
||||
project.add_maintainer(user_jacob)
|
||||
sign_in(user)
|
||||
create(:issue, project: project)
|
||||
|
||||
|
@ -40,113 +19,23 @@ describe 'Dropdown author', :js do
|
|||
end
|
||||
|
||||
describe 'behavior' do
|
||||
it 'opens when the search bar has author:' do
|
||||
filtered_search.set('author:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: true)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: false)
|
||||
end
|
||||
|
||||
it 'shows loading indicator when opened' do
|
||||
slow_requests do
|
||||
filtered_search.set('author:')
|
||||
|
||||
expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'hides loading indicator when loaded' do
|
||||
send_keys_to_filtered_search('author:')
|
||||
|
||||
expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading')
|
||||
end
|
||||
|
||||
it 'loads all the authors when opened' do
|
||||
send_keys_to_filtered_search('author:')
|
||||
input_filtered_search('author:', submit: false, extra_space: false)
|
||||
|
||||
expect(dropdown_author_size).to eq(4)
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 2)
|
||||
end
|
||||
|
||||
it 'shows current user at top of dropdown' do
|
||||
send_keys_to_filtered_search('author:')
|
||||
input_filtered_search('author:', submit: false, extra_space: false)
|
||||
|
||||
expect(first('#js-dropdown-author li')).to have_content(user.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filtering' do
|
||||
before do
|
||||
filtered_search.set('author')
|
||||
send_keys_to_filtered_search(':')
|
||||
end
|
||||
|
||||
it 'filters by name' do
|
||||
send_keys_to_filtered_search('jac')
|
||||
|
||||
expect(dropdown_author_size).to eq(1)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive name' do
|
||||
send_keys_to_filtered_search('Jac')
|
||||
|
||||
expect(dropdown_author_size).to eq(1)
|
||||
end
|
||||
|
||||
it 'filters by username with symbol' do
|
||||
send_keys_to_filtered_search('@oot')
|
||||
|
||||
expect(dropdown_author_size).to eq(2)
|
||||
end
|
||||
|
||||
it 'filters by username without symbol' do
|
||||
send_keys_to_filtered_search('oot')
|
||||
|
||||
expect(dropdown_author_size).to eq(2)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive username without symbol' do
|
||||
send_keys_to_filtered_search('OOT')
|
||||
|
||||
expect(dropdown_author_size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown' do
|
||||
before do
|
||||
filtered_search.set('author')
|
||||
send_keys_to_filtered_search(':')
|
||||
end
|
||||
|
||||
it 'fills in the author username when the author has not been filtered' do
|
||||
click_author(user_jacob.name)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: false)
|
||||
expect_tokens([author_token(user_jacob.name)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the author username when the author has been filtered' do
|
||||
click_author(user.name)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: false)
|
||||
expect_tokens([author_token(user.name)])
|
||||
expect_filtered_search_input_empty
|
||||
expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown without Ajax call' do
|
||||
before do
|
||||
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
|
||||
filtered_search.set('author:')
|
||||
input_filtered_search('author:', submit: false, extra_space: false)
|
||||
end
|
||||
|
||||
after do
|
||||
|
@ -154,55 +43,11 @@ describe 'Dropdown author', :js do
|
|||
end
|
||||
|
||||
it 'selects current user' do
|
||||
find('#js-dropdown-author .filter-dropdown-item', text: user.username).click
|
||||
find("#{js_dropdown_author} .filter-dropdown-item", text: user.username).click
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: false)
|
||||
expect_tokens([author_token(user.username)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'input has existing content' do
|
||||
it 'opens author dropdown with existing search term' do
|
||||
filtered_search.set('searchTerm author:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: true)
|
||||
end
|
||||
|
||||
it 'opens author dropdown with existing assignee' do
|
||||
filtered_search.set('assignee:@user author:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: true)
|
||||
end
|
||||
|
||||
it 'opens author dropdown with existing label' do
|
||||
filtered_search.set('label:~bug author:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: true)
|
||||
end
|
||||
|
||||
it 'opens author dropdown with existing milestone' do
|
||||
filtered_search.set('milestone:%v1.0 author:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_author, visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
filtered_search.set('author')
|
||||
send_keys_to_filtered_search(':')
|
||||
initial_size = dropdown_author_size
|
||||
|
||||
expect(initial_size).to be > 0
|
||||
|
||||
new_user = create(:user)
|
||||
project.add_maintainer(new_user)
|
||||
find('.filtered-search-box .clear-search').click
|
||||
filtered_search.set('author')
|
||||
send_keys_to_filtered_search(':')
|
||||
|
||||
expect(dropdown_author_size).to eq(initial_size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
58
spec/features/issues/filtered_search/dropdown_base_spec.rb
Normal file
58
spec/features/issues/filtered_search/dropdown_base_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Dropdown base', :js do
|
||||
include FilteredSearchHelpers
|
||||
|
||||
let!(:project) { create(:project) }
|
||||
let!(:user) { create(:user, name: 'administrator', username: 'root') }
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
|
||||
let(:filter_dropdown) { find("#{js_dropdown_assignee} .filter-dropdown") }
|
||||
|
||||
def dropdown_assignee_size
|
||||
filter_dropdown.all('.filter-dropdown-item').size
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
create(:issue, project: project)
|
||||
|
||||
visit project_issues_path(project)
|
||||
end
|
||||
|
||||
describe 'behavior' do
|
||||
it 'shows loading indicator when opened' do
|
||||
slow_requests do
|
||||
# We aren't using `input_filtered_search` because we want to see the loading indicator
|
||||
filtered_search.set('assignee:')
|
||||
|
||||
expect(page).to have_css("#{js_dropdown_assignee} .filter-dropdown-loading", visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'hides loading indicator when loaded' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
initial_size = dropdown_assignee_size
|
||||
|
||||
expect(initial_size).to be > 0
|
||||
|
||||
new_user = create(:user)
|
||||
project.add_maintainer(new_user)
|
||||
find('.filtered-search-box .clear-search').click
|
||||
input_filtered_search('assignee:', submit: false, extra_space: false)
|
||||
|
||||
expect(dropdown_assignee_size).to eq(initial_size)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,30 +11,13 @@ describe 'Dropdown emoji', :js do
|
|||
let!(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: issue) }
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_emoji) { '#js-dropdown-my-reaction' }
|
||||
|
||||
def send_keys_to_filtered_search(input)
|
||||
input.split("").each do |i|
|
||||
filtered_search.send_keys(i)
|
||||
end
|
||||
|
||||
sleep 0.5
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def dropdown_emoji_size
|
||||
all('gl-emoji[data-name]').size
|
||||
end
|
||||
|
||||
def click_emoji(text)
|
||||
find('#js-dropdown-my-reaction .filter-dropdown .filter-dropdown-item', text: text).click
|
||||
end
|
||||
let(:filter_dropdown) { find("#{js_dropdown_emoji} .filter-dropdown") }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
create_list(:award_emoji, 2, user: user, name: 'thumbsup')
|
||||
create_list(:award_emoji, 1, user: user, name: 'thumbsdown')
|
||||
create_list(:award_emoji, 3, user: user, name: 'star')
|
||||
create_list(:award_emoji, 1, user: user, name: 'tea')
|
||||
end
|
||||
|
||||
context 'when user not logged in' do
|
||||
|
@ -65,137 +48,16 @@ describe 'Dropdown emoji', :js do
|
|||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||
end
|
||||
|
||||
it 'shows loading indicator when opened' do
|
||||
slow_requests do
|
||||
filtered_search.set('my-reaction:')
|
||||
|
||||
expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
it 'hides loading indicator when loaded' do
|
||||
send_keys_to_filtered_search('my-reaction:')
|
||||
|
||||
expect(page).not_to have_css('#js-dropdown-my-reaction .filter-dropdown-loading')
|
||||
end
|
||||
|
||||
it 'loads all the emojis when opened' do
|
||||
send_keys_to_filtered_search('my-reaction:')
|
||||
input_filtered_search('my-reaction:', submit: false, extra_space: false)
|
||||
|
||||
expect(dropdown_emoji_size).to eq(4)
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 3)
|
||||
end
|
||||
|
||||
it 'shows the most populated emoji at top of dropdown' do
|
||||
send_keys_to_filtered_search('my-reaction:')
|
||||
input_filtered_search('my-reaction:', submit: false, extra_space: false)
|
||||
|
||||
expect(first('#js-dropdown-my-reaction .filter-dropdown li')).to have_content(award_emoji_star.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filtering' do
|
||||
before do
|
||||
filtered_search.set('my-reaction')
|
||||
send_keys_to_filtered_search(':')
|
||||
end
|
||||
|
||||
it 'filters by name' do
|
||||
send_keys_to_filtered_search('up')
|
||||
|
||||
expect(dropdown_emoji_size).to eq(1)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive name' do
|
||||
send_keys_to_filtered_search('Up')
|
||||
|
||||
expect(dropdown_emoji_size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown' do
|
||||
before do
|
||||
filtered_search.set('my-reaction')
|
||||
send_keys_to_filtered_search(':')
|
||||
end
|
||||
|
||||
it 'selects `None`' do
|
||||
find('#js-dropdown-my-reaction .filter-dropdown-item', text: 'None').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||
expect_tokens([reaction_token('None', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `Any`' do
|
||||
find('#js-dropdown-my-reaction .filter-dropdown-item', text: 'Any').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||
expect_tokens([reaction_token('Any', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the my-reaction name' do
|
||||
click_emoji('thumbsup')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: false)
|
||||
expect_tokens([reaction_token('thumbsup')])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'input has existing content' do
|
||||
it 'opens my-reaction dropdown with existing search term' do
|
||||
filtered_search.set('searchTerm my-reaction:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
|
||||
it 'opens my-reaction dropdown with existing assignee' do
|
||||
filtered_search.set('assignee:@user my-reaction:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
|
||||
it 'opens my-reaction dropdown with existing label' do
|
||||
filtered_search.set('label:~bug my-reaction:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
|
||||
it 'opens my-reaction dropdown with existing milestone' do
|
||||
filtered_search.set('milestone:%v1.0 my-reaction:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
|
||||
it 'opens my-reaction dropdown with existing my-reaction' do
|
||||
filtered_search.set('my-reaction:star my-reaction:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_emoji, visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
filtered_search.set('my-reaction')
|
||||
send_keys_to_filtered_search(':')
|
||||
initial_size = dropdown_emoji_size
|
||||
|
||||
expect(initial_size).to be > 0
|
||||
|
||||
create_list(:award_emoji, 1, user: user, name: 'smile')
|
||||
find('.filtered-search-box .clear-search').click
|
||||
filtered_search.set('my-reaction')
|
||||
send_keys_to_filtered_search(':')
|
||||
|
||||
expect(dropdown_emoji_size).to eq(initial_size)
|
||||
expect(first("#{js_dropdown_emoji} .filter-dropdown li")).to have_content(award_emoji_star.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,9 +46,7 @@ describe 'Dropdown hint', :js do
|
|||
|
||||
it 'opens when the search bar is first focused' do
|
||||
expect(page).to have_css(js_dropdown_hint, visible: true)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
|
@ -77,7 +75,7 @@ describe 'Dropdown hint', :js do
|
|||
filtered_search.click
|
||||
end
|
||||
|
||||
it 'opens the author dropdown when you click on author' do
|
||||
it 'opens the token dropdown when you click on it' do
|
||||
click_hint('author')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
|
@ -85,116 +83,10 @@ describe 'Dropdown hint', :js do
|
|||
expect_tokens([{ name: 'Author' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the assignee dropdown when you click on assignee' do
|
||||
click_hint('assignee')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-assignee', visible: true)
|
||||
expect_tokens([{ name: 'Assignee' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the milestone dropdown when you click on milestone' do
|
||||
click_hint('milestone')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-milestone', visible: true)
|
||||
expect_tokens([{ name: 'Milestone' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the release dropdown when you click on release' do
|
||||
click_hint('release')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-release', visible: true)
|
||||
expect_tokens([{ name: 'Release' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the label dropdown when you click on label' do
|
||||
click_hint('label')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-label', visible: true)
|
||||
expect_tokens([{ name: 'Label' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the emoji dropdown when you click on my-reaction' do
|
||||
click_hint('my-reaction')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
|
||||
expect_tokens([{ name: 'My-reaction' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the yes-no dropdown when you click on confidential' do
|
||||
click_hint('confidential')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-confidential', visible: true)
|
||||
expect_tokens([{ name: 'Confidential' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown with some input' do
|
||||
it 'opens the author dropdown when you click on author' do
|
||||
filtered_search.set('auth')
|
||||
click_hint('author')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-author', visible: true)
|
||||
expect_tokens([{ name: 'Author' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the assignee dropdown when you click on assignee' do
|
||||
filtered_search.set('assign')
|
||||
click_hint('assignee')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-assignee', visible: true)
|
||||
expect_tokens([{ name: 'Assignee' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the milestone dropdown when you click on milestone' do
|
||||
filtered_search.set('mile')
|
||||
click_hint('milestone')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-milestone', visible: true)
|
||||
expect_tokens([{ name: 'Milestone' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the label dropdown when you click on label' do
|
||||
filtered_search.set('lab')
|
||||
click_hint('label')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-label', visible: true)
|
||||
expect_tokens([{ name: 'Label' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'opens the emoji dropdown when you click on my-reaction' do
|
||||
filtered_search.set('my')
|
||||
click_hint('my-reaction')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-my-reaction', visible: true)
|
||||
expect_tokens([{ name: 'My-reaction' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'reselecting from dropdown' do
|
||||
it 'reuses existing author text' do
|
||||
it 'reuses existing token text' do
|
||||
filtered_search.send_keys('author:')
|
||||
filtered_search.send_keys(:backspace)
|
||||
filtered_search.send_keys(:backspace)
|
||||
|
@ -203,63 +95,6 @@ describe 'Dropdown hint', :js do
|
|||
expect_tokens([{ name: 'Author' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'reuses existing assignee text' do
|
||||
filtered_search.send_keys('assignee:')
|
||||
filtered_search.send_keys(:backspace)
|
||||
filtered_search.send_keys(:backspace)
|
||||
click_hint('assignee')
|
||||
|
||||
expect_tokens([{ name: 'Assignee' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'reuses existing milestone text' do
|
||||
filtered_search.send_keys('milestone:')
|
||||
filtered_search.send_keys(:backspace)
|
||||
filtered_search.send_keys(:backspace)
|
||||
click_hint('milestone')
|
||||
|
||||
expect_tokens([{ name: 'Milestone' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'reuses existing label text' do
|
||||
filtered_search.send_keys('label:')
|
||||
filtered_search.send_keys(:backspace)
|
||||
filtered_search.send_keys(:backspace)
|
||||
click_hint('label')
|
||||
|
||||
expect_tokens([{ name: 'Label' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'reuses existing emoji text' do
|
||||
filtered_search.send_keys('my-reaction:')
|
||||
filtered_search.send_keys(:backspace)
|
||||
filtered_search.send_keys(:backspace)
|
||||
click_hint('my-reaction')
|
||||
|
||||
expect_tokens([{ name: 'My-reaction' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'merge request page' do
|
||||
before do
|
||||
sign_in(user)
|
||||
visit project_merge_requests_path(project)
|
||||
filtered_search.click
|
||||
end
|
||||
|
||||
it 'shows the WIP menu item and opens the WIP options dropdown' do
|
||||
click_hint('wip')
|
||||
|
||||
expect(page).to have_css(js_dropdown_hint, visible: false)
|
||||
expect(page).to have_css('#js-dropdown-wip', visible: true)
|
||||
expect_tokens([{ name: 'WIP' }])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,31 +8,7 @@ describe 'Dropdown label', :js do
|
|||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_label) { '#js-dropdown-label' }
|
||||
let(:filter_dropdown) { find("#{js_dropdown_label} .filter-dropdown") }
|
||||
|
||||
shared_context 'with labels' do
|
||||
let!(:bug_label) { create(:label, project: project, title: 'bug-label') }
|
||||
let!(:uppercase_label) { create(:label, project: project, title: 'BUG-LABEL') }
|
||||
let!(:two_words_label) { create(:label, project: project, title: 'High Priority') }
|
||||
let!(:wont_fix_label) { create(:label, project: project, title: 'Won"t Fix') }
|
||||
let!(:wont_fix_single_label) { create(:label, project: project, title: 'Won\'t Fix') }
|
||||
let!(:special_label) { create(:label, project: project, title: '!@#$%^+&*()') }
|
||||
let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title') }
|
||||
end
|
||||
|
||||
def search_for_label(label)
|
||||
init_label_search
|
||||
filtered_search.send_keys(label)
|
||||
end
|
||||
|
||||
def click_label(text)
|
||||
filter_dropdown.find('.filter-dropdown-item', text: text).click
|
||||
end
|
||||
|
||||
def clear_search_field
|
||||
find('.filtered-search-box .clear-search').click
|
||||
end
|
||||
let(:filter_dropdown) { find('#js-dropdown-label .filter-dropdown') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -42,267 +18,12 @@ describe 'Dropdown label', :js do
|
|||
visit project_issues_path(project)
|
||||
end
|
||||
|
||||
describe 'keyboard navigation' do
|
||||
it 'selects label' do
|
||||
bug_label = create(:label, project: project, title: 'bug-label')
|
||||
init_label_search
|
||||
|
||||
# navigate to the bug_label option and selects it
|
||||
filtered_search.native.send_keys(:down, :down, :down, :enter)
|
||||
|
||||
expect_tokens([label_token(bug_label.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'behavior' do
|
||||
it 'opens when the search bar has label:' do
|
||||
filtered_search.set('label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'shows loading indicator when opened and hides it when loaded' do
|
||||
slow_requests do
|
||||
filtered_search.set('label:')
|
||||
|
||||
expect(page).to have_css("#{js_dropdown_label} .filter-dropdown-loading", visible: true)
|
||||
end
|
||||
expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading')
|
||||
end
|
||||
|
||||
it 'loads all the labels when opened' do
|
||||
bug_label = create(:label, project: project, title: 'bug-label')
|
||||
create(:label, project: project, title: 'bug-label')
|
||||
filtered_search.set('label:')
|
||||
|
||||
expect(filter_dropdown).to have_content(bug_label.title)
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filtering' do
|
||||
include_context 'with labels'
|
||||
|
||||
before do
|
||||
init_label_search
|
||||
end
|
||||
|
||||
it 'filters by case-insensitive name with or without symbol' do
|
||||
filtered_search.send_keys('b')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 2)
|
||||
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
filtered_search.send_keys('~bu')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: bug_label.title)).to be_visible
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: uppercase_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 2)
|
||||
end
|
||||
|
||||
it 'filters by multiple words with or without symbol' do
|
||||
filtered_search.send_keys('Hig')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
filtered_search.send_keys('~Hig')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: two_words_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by multiple words containing single quotes with or without symbol' do
|
||||
filtered_search.send_keys('won\'t')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
filtered_search.send_keys('~won\'t')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_single_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by multiple words containing double quotes with or without symbol' do
|
||||
filtered_search.send_keys('won"t')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
filtered_search.send_keys('~won"t')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: wont_fix_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by special characters with or without symbol' do
|
||||
filtered_search.send_keys('^+')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
filtered_search.send_keys('~^+')
|
||||
|
||||
expect(filter_dropdown.find('.filter-dropdown-item', text: special_label.title)).to be_visible
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown' do
|
||||
include_context 'with labels'
|
||||
|
||||
before do
|
||||
init_label_search
|
||||
end
|
||||
|
||||
it 'fills in the label name when the label has not been filled' do
|
||||
click_label(bug_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token(bug_label.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name when the label is partially filled' do
|
||||
filtered_search.send_keys('bu')
|
||||
click_label(bug_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token(bug_label.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name that contains multiple words' do
|
||||
click_label(two_words_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token("\"#{two_words_label.title}\"")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name that contains multiple words and is very long' do
|
||||
click_label(long_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token("\"#{long_label.title}\"")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name that contains double quotes' do
|
||||
click_label(wont_fix_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token("'#{wont_fix_label.title}'")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name with the correct capitalization' do
|
||||
click_label(uppercase_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token(uppercase_label.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the label name with special characters' do
|
||||
click_label(special_label.title)
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token(special_label.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `no label`' do
|
||||
find("#{js_dropdown_label} .filter-dropdown-item", text: 'None').click
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token('None', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `any label`' do
|
||||
find("#{js_dropdown_label} .filter-dropdown-item", text: 'Any').click
|
||||
|
||||
expect(page).not_to have_css(js_dropdown_label)
|
||||
expect_tokens([label_token('Any', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'input has existing content' do
|
||||
it 'opens label dropdown with existing search term' do
|
||||
filtered_search.set('searchTerm label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'opens label dropdown with existing author' do
|
||||
filtered_search.set('author:@person label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'opens label dropdown with existing assignee' do
|
||||
filtered_search.set('assignee:@person label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'opens label dropdown with existing label' do
|
||||
filtered_search.set('label:~urgent label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'opens label dropdown with existing milestone' do
|
||||
filtered_search.set('milestone:%v2.0 label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
|
||||
it 'opens label dropdown with existing my-reaction' do
|
||||
filtered_search.set('my-reaction:star label:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_label)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
create(:label, project: project, title: 'bug-label')
|
||||
init_label_search
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
|
||||
create(:label, project: project)
|
||||
clear_search_field
|
||||
init_label_search
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1)
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,26 +9,9 @@ describe 'Dropdown milestone', :js do
|
|||
let!(:user) { create(:user) }
|
||||
let!(:milestone) { create(:milestone, title: 'v1.0', project: project) }
|
||||
let!(:uppercase_milestone) { create(:milestone, title: 'CAP_MILESTONE', project: project) }
|
||||
let!(:two_words_milestone) { create(:milestone, title: 'Future Plan', project: project) }
|
||||
let!(:wont_fix_milestone) { create(:milestone, title: 'Won"t Fix', project: project) }
|
||||
let!(:special_milestone) { create(:milestone, title: '!@#$%^&*(+)', project: project) }
|
||||
let!(:long_milestone) { create(:milestone, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title', project: project) }
|
||||
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:js_dropdown_milestone) { '#js-dropdown-milestone' }
|
||||
let(:filter_dropdown) { find("#{js_dropdown_milestone} .filter-dropdown") }
|
||||
|
||||
def dropdown_milestone_size
|
||||
filter_dropdown.all('.filter-dropdown-item').size
|
||||
end
|
||||
|
||||
def click_milestone(text)
|
||||
find('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item', text: text).click
|
||||
end
|
||||
|
||||
def click_static_milestone(text)
|
||||
find('#js-dropdown-milestone .filter-dropdown-item', text: text).click
|
||||
end
|
||||
let(:filter_dropdown) { find('#js-dropdown-milestone .filter-dropdown') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -39,240 +22,12 @@ describe 'Dropdown milestone', :js do
|
|||
end
|
||||
|
||||
describe 'behavior' do
|
||||
context 'filters by "milestone:"' do
|
||||
before do
|
||||
filtered_search.set('milestone:')
|
||||
end
|
||||
|
||||
it 'opens when the search bar has milestone:' do
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'closes when the search bar is unfocused' do
|
||||
find('body').click
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
end
|
||||
|
||||
it 'hides loading indicator when loaded' do
|
||||
expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading')
|
||||
end
|
||||
|
||||
it 'loads all the milestones when opened' do
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows loading indicator when opened' do
|
||||
slow_requests do
|
||||
filtered_search.set('milestone:')
|
||||
|
||||
expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filtering' do
|
||||
before do
|
||||
filtered_search.set('milestone:')
|
||||
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(uppercase_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(two_words_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(wont_fix_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(special_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(long_milestone.title)
|
||||
end
|
||||
|
||||
it 'filters by name' do
|
||||
filtered_search.send_keys('v1')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive name' do
|
||||
filtered_search.send_keys('V1')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by name with symbol' do
|
||||
filtered_search.send_keys('%v1')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by case insensitive name with symbol' do
|
||||
filtered_search.send_keys('%V1')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by special characters' do
|
||||
filtered_search.send_keys('(+')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'filters by special characters with symbol' do
|
||||
filtered_search.send_keys('%(+')
|
||||
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'selecting from dropdown' do
|
||||
before do
|
||||
filtered_search.set('milestone:')
|
||||
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(uppercase_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(two_words_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(wont_fix_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(special_milestone.title)
|
||||
expect(find("#{js_dropdown_milestone} .filter-dropdown")).to have_content(long_milestone.title)
|
||||
end
|
||||
|
||||
it 'fills in the milestone name when the milestone has not been filled' do
|
||||
click_milestone(milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token(milestone.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name when the milestone is partially filled', :quarantine do
|
||||
filtered_search.send_keys('v')
|
||||
click_milestone(milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token(milestone.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name that contains multiple words' do
|
||||
click_milestone(two_words_milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token("\"#{two_words_milestone.title}\"")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name that contains multiple words and is very long' do
|
||||
click_milestone(long_milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token("\"#{long_milestone.title}\"")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name that contains double quotes' do
|
||||
click_milestone(wont_fix_milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token("'#{wont_fix_milestone.title}'")])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name with the correct capitalization' do
|
||||
click_milestone(uppercase_milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token(uppercase_milestone.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'fills in the milestone name with special characters' do
|
||||
click_milestone(special_milestone.title)
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token(special_milestone.title)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `no milestone`' do
|
||||
click_static_milestone('None')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token('None', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `any milestone`' do
|
||||
click_static_milestone('Any')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token('Any', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `upcoming milestone`' do
|
||||
click_static_milestone('Upcoming')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token('Upcoming', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
|
||||
it 'selects `started milestones`' do
|
||||
click_static_milestone('Started')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: false)
|
||||
expect_tokens([milestone_token('Started', false)])
|
||||
expect_filtered_search_input_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'input has existing content' do
|
||||
it 'opens milestone dropdown with existing search term' do
|
||||
filtered_search.set('searchTerm milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown with existing author' do
|
||||
filtered_search.set('author:@john milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown with existing assignee' do
|
||||
filtered_search.set('assignee:@john milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown with existing label' do
|
||||
filtered_search.set('label:~important milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown with existing milestone' do
|
||||
filtered_search.set('milestone:%100 milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown with existing my-reaction' do
|
||||
filtered_search.set('my-reaction:star milestone:')
|
||||
|
||||
expect(page).to have_css(js_dropdown_milestone, visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'caching requests' do
|
||||
it 'caches requests after the first load' do
|
||||
filtered_search.set('milestone:')
|
||||
initial_size = dropdown_milestone_size
|
||||
|
||||
expect(initial_size).to be > 0
|
||||
|
||||
create(:milestone, project: project)
|
||||
find('.filtered-search-box .clear-search').click
|
||||
filtered_search.set('milestone:')
|
||||
|
||||
expect(dropdown_milestone_size).to eq(initial_size)
|
||||
it 'loads all the milestones when opened' do
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,13 +10,8 @@ describe 'Dropdown release', :js do
|
|||
let!(:release) { create(:release, tag: 'v1.0', project: project) }
|
||||
let!(:crazy_release) { create(:release, tag: '☺!/"#%&\'{}+,-.<>;=@]_`{|}🚀', project: project) }
|
||||
|
||||
def filtered_search
|
||||
find('.filtered-search')
|
||||
end
|
||||
|
||||
def filter_dropdown
|
||||
find('#js-dropdown-release .filter-dropdown')
|
||||
end
|
||||
let(:filtered_search) { find('.filtered-search') }
|
||||
let(:filter_dropdown) { find('#js-dropdown-release .filter-dropdown') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -31,25 +26,8 @@ describe 'Dropdown release', :js do
|
|||
filtered_search.set('release:')
|
||||
end
|
||||
|
||||
def expect_results(count)
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: count)
|
||||
end
|
||||
|
||||
it 'loads all the releases when opened' do
|
||||
expect_results(2)
|
||||
end
|
||||
|
||||
it 'filters by tag name' do
|
||||
filtered_search.send_keys("☺")
|
||||
expect_results(1)
|
||||
end
|
||||
|
||||
it 'fills in the release name when the autocomplete hint is clicked' do
|
||||
find('#js-dropdown-release .filter-dropdown-item', text: crazy_release.tag).click
|
||||
|
||||
expect(page).to have_css('#js-dropdown-release', visible: false)
|
||||
expect_tokens([release_token(crazy_release.tag)])
|
||||
expect_filtered_search_input_empty
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -475,58 +475,6 @@ describe 'Filter issues', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'RSS feeds' do
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
shared_examples 'updates atom feed link' do |type|
|
||||
it "for #{type}" do
|
||||
visit path
|
||||
|
||||
link = find_link('Subscribe to RSS feed')
|
||||
params = CGI.parse(URI.parse(link[:href]).query)
|
||||
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
|
||||
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
|
||||
|
||||
expected = {
|
||||
'feed_token' => [user.feed_token],
|
||||
'milestone_title' => [milestone.title],
|
||||
'assignee_id' => [user.id.to_s]
|
||||
}
|
||||
|
||||
expect(params).to include(expected)
|
||||
expect(auto_discovery_params).to include(expected)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'updates atom feed link', :project do
|
||||
let(:path) { project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id) }
|
||||
end
|
||||
|
||||
it_behaves_like 'updates atom feed link', :group do
|
||||
let(:path) { issues_group_path(group, milestone_title: milestone.title, assignee_id: user.id) }
|
||||
end
|
||||
|
||||
it 'updates atom feed link for group issues' do
|
||||
visit issues_group_path(group, milestone_title: milestone.title, assignee_id: user.id)
|
||||
link = find('.nav-controls a[title="Subscribe to RSS feed"]', visible: false)
|
||||
params = CGI.parse(URI.parse(link[:href]).query)
|
||||
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
|
||||
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
|
||||
|
||||
expect(params).to include('feed_token' => [user.feed_token])
|
||||
expect(params).to include('milestone_title' => [milestone.title])
|
||||
expect(params).to include('assignee_id' => [user.id.to_s])
|
||||
expect(auto_discovery_params).to include('feed_token' => [user.feed_token])
|
||||
expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
|
||||
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
|
||||
end
|
||||
end
|
||||
|
||||
context 'URL has a trailing slash' do
|
||||
before do
|
||||
visit "#{project_issues_path(project)}/"
|
||||
|
|
|
@ -34,7 +34,7 @@ describe 'Visual tokens', :js do
|
|||
visit project_issues_path(project)
|
||||
end
|
||||
|
||||
describe 'editing author token' do
|
||||
describe 'editing a single token' do
|
||||
before do
|
||||
input_filtered_search('author:@root assignee:none', submit: false)
|
||||
first('.tokens-container .filtered-search-token').click
|
||||
|
@ -42,9 +42,6 @@ describe 'Visual tokens', :js do
|
|||
|
||||
it 'opens author dropdown' do
|
||||
expect(page).to have_css('#js-dropdown-author', visible: true)
|
||||
end
|
||||
|
||||
it 'makes value editable' do
|
||||
expect_filtered_search_input('@root')
|
||||
end
|
||||
|
||||
|
@ -77,139 +74,6 @@ describe 'Visual tokens', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'editing assignee token' do
|
||||
before do
|
||||
input_filtered_search('assignee:@root author:none', submit: false)
|
||||
first('.tokens-container .filtered-search-token').double_click
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown' do
|
||||
expect(page).to have_css('#js-dropdown-assignee', visible: true)
|
||||
end
|
||||
|
||||
it 'makes value editable' do
|
||||
expect_filtered_search_input('@root')
|
||||
end
|
||||
|
||||
it 'filters value' do
|
||||
filtered_search.send_keys(:backspace)
|
||||
|
||||
expect(page).to have_css('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'ends editing mode when document is clicked' do
|
||||
find('#content-body').click
|
||||
|
||||
expect_filtered_search_input_empty
|
||||
expect(page).to have_css('#js-dropdown-assignee', visible: false)
|
||||
end
|
||||
|
||||
describe 'selecting static option from dropdown' do
|
||||
before do
|
||||
find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click
|
||||
end
|
||||
|
||||
it 'changes value in visual token' do
|
||||
expect(first('.tokens-container .filtered-search-token .value').text).to eq('None')
|
||||
end
|
||||
|
||||
it 'moves input to the right' do
|
||||
expect(is_input_focused).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'editing milestone token' do
|
||||
before do
|
||||
input_filtered_search('milestone:%10.0 author:none', submit: false)
|
||||
first('.tokens-container .filtered-search-token').click
|
||||
first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item')
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown' do
|
||||
expect(filter_milestone_dropdown.find('.filter-dropdown-item', text: milestone_ten.title)).to be_visible
|
||||
expect(filter_milestone_dropdown.find('.filter-dropdown-item', text: milestone_nine.title)).to be_visible
|
||||
expect(page).to have_css('#js-dropdown-milestone', visible: true)
|
||||
end
|
||||
|
||||
it 'selects static option from dropdown' do
|
||||
find("#js-dropdown-milestone").find('.filter-dropdown-item', text: 'Upcoming').click
|
||||
|
||||
expect(first('.tokens-container .filtered-search-token .value').text).to eq('Upcoming')
|
||||
expect(is_input_focused).to eq(true)
|
||||
end
|
||||
|
||||
it 'makes value editable' do
|
||||
expect_filtered_search_input('%10.0')
|
||||
end
|
||||
|
||||
it 'filters value' do
|
||||
filtered_search.send_keys(:backspace)
|
||||
|
||||
expect(page).to have_css('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item', count: 1)
|
||||
end
|
||||
|
||||
it 'ends editing mode when document is clicked' do
|
||||
find('#content-body').click
|
||||
|
||||
expect_filtered_search_input_empty
|
||||
expect(page).to have_css('#js-dropdown-milestone', visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'editing label token' do
|
||||
before do
|
||||
input_filtered_search("label:~#{label.title} author:none", submit: false)
|
||||
first('.tokens-container .filtered-search-token').double_click
|
||||
first('#js-dropdown-label .filter-dropdown .filter-dropdown-item')
|
||||
end
|
||||
|
||||
it 'opens label dropdown' do
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
|
||||
expect(page).to have_css('#js-dropdown-label', visible: true)
|
||||
end
|
||||
|
||||
it 'selects option from dropdown' do
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
|
||||
|
||||
find("#js-dropdown-label").find('.filter-dropdown-item', text: cc_label.title).click
|
||||
|
||||
expect(first('.tokens-container .filtered-search-token .value').text).to eq("~\"#{cc_label.title}\"")
|
||||
expect(is_input_focused).to eq(true)
|
||||
end
|
||||
|
||||
it 'makes value editable' do
|
||||
expect_filtered_search_input("~#{label.title}")
|
||||
end
|
||||
|
||||
it 'filters value' do
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
|
||||
expect(filter_label_dropdown.find('.filter-dropdown-item', text: cc_label.title)).to be_visible
|
||||
|
||||
filtered_search.send_keys(:backspace)
|
||||
|
||||
filter_label_dropdown.find('.filter-dropdown-item')
|
||||
|
||||
expect(page.all('#js-dropdown-label .filter-dropdown .filter-dropdown-item').size).to eq(1)
|
||||
end
|
||||
|
||||
it 'ends editing mode when document is clicked' do
|
||||
find('#content-body').click
|
||||
|
||||
expect_filtered_search_input_empty
|
||||
expect(page).to have_css('#js-dropdown-label', visible: false)
|
||||
end
|
||||
|
||||
it 'ends editing mode when scroll container is clicked' do
|
||||
find('.scroll-container').click
|
||||
|
||||
expect_filtered_search_input_empty
|
||||
expect(page).to have_css('#js-dropdown-label', visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'editing multiple tokens' do
|
||||
before do
|
||||
input_filtered_search('author:@root assignee:none', submit: false)
|
||||
|
@ -232,10 +96,6 @@ describe 'Visual tokens', :js do
|
|||
first('.tokens-container .filtered-search-term').double_click
|
||||
end
|
||||
|
||||
it 'opens hint dropdown' do
|
||||
expect(page).to have_css('#js-dropdown-hint', visible: true)
|
||||
end
|
||||
|
||||
it 'opens author dropdown' do
|
||||
find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: 'author').click
|
||||
|
||||
|
@ -255,59 +115,21 @@ describe 'Visual tokens', :js do
|
|||
expect(page).to have_css('#js-dropdown-hint', visible: true)
|
||||
end
|
||||
|
||||
it 'opens author dropdown' do
|
||||
it 'opens token dropdown' do
|
||||
filtered_search.send_keys('author:')
|
||||
|
||||
expect(page).to have_css('#js-dropdown-author', visible: true)
|
||||
end
|
||||
|
||||
it 'opens assignee dropdown' do
|
||||
filtered_search.send_keys('assignee:')
|
||||
expect(page).to have_css('#js-dropdown-assignee', visible: true)
|
||||
end
|
||||
|
||||
it 'opens milestone dropdown' do
|
||||
filtered_search.send_keys('milestone:')
|
||||
expect(page).to have_css('#js-dropdown-milestone', visible: true)
|
||||
end
|
||||
|
||||
it 'opens label dropdown' do
|
||||
filtered_search.send_keys('label:')
|
||||
expect(page).to have_css('#js-dropdown-label', visible: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'creates visual tokens' do
|
||||
it 'creates author token' do
|
||||
describe 'visual tokens' do
|
||||
it 'creates visual token' do
|
||||
filtered_search.send_keys('author:@thomas ')
|
||||
token = page.all('.tokens-container .filtered-search-token')[1]
|
||||
|
||||
expect(token.find('.name').text).to eq('Author')
|
||||
expect(token.find('.value').text).to eq('@thomas')
|
||||
end
|
||||
|
||||
it 'creates assignee token' do
|
||||
filtered_search.send_keys('assignee:@thomas ')
|
||||
token = page.all('.tokens-container .filtered-search-token')[1]
|
||||
|
||||
expect(token.find('.name').text).to eq('Assignee')
|
||||
expect(token.find('.value').text).to eq('@thomas')
|
||||
end
|
||||
|
||||
it 'creates milestone token' do
|
||||
filtered_search.send_keys('milestone:none ')
|
||||
token = page.all('.tokens-container .filtered-search-token')[1]
|
||||
|
||||
expect(token.find('.name').text).to eq('Milestone')
|
||||
expect(token.find('.value').text).to eq('none')
|
||||
end
|
||||
|
||||
it 'creates label token' do
|
||||
filtered_search.send_keys('label:~Backend ')
|
||||
token = page.all('.tokens-container .filtered-search-token')[1]
|
||||
|
||||
expect(token.find('.name').text).to eq('Label')
|
||||
expect(token.find('.value').text).to eq('~Backend')
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not tokenize incomplete token' do
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Project Issues RSS' do
|
||||
let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
|
||||
let!(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
|
||||
let(:path) { project_issues_path(project) }
|
||||
|
||||
before do
|
||||
create(:issue, project: project)
|
||||
create(:issue, project: project, assignees: [user])
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
|
@ -31,4 +34,34 @@ describe 'Project Issues RSS' do
|
|||
it_behaves_like "it has an RSS button without a feed token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
|
||||
describe 'feeds' do
|
||||
shared_examples 'updates atom feed link' do |type|
|
||||
it "for #{type}" do
|
||||
sign_in(user)
|
||||
visit path
|
||||
|
||||
link = find_link('Subscribe to RSS feed')
|
||||
params = CGI.parse(URI.parse(link[:href]).query)
|
||||
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
|
||||
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
|
||||
|
||||
expected = {
|
||||
'feed_token' => [user.feed_token],
|
||||
'assignee_id' => [user.id.to_s]
|
||||
}
|
||||
|
||||
expect(params).to include(expected)
|
||||
expect(auto_discovery_params).to include(expected)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'updates atom feed link', :project do
|
||||
let(:path) { project_issues_path(project, assignee_id: user.id) }
|
||||
end
|
||||
|
||||
it_behaves_like 'updates atom feed link', :group do
|
||||
let(:path) { issues_group_path(group, assignee_id: user.id) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import Vuex from 'vuex';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import { createStore } from '~/ide/stores';
|
||||
import EditorHeader from '~/ide/components/commit_sidebar/editor_header.vue';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('IDE commit editor header', () => {
|
||||
let wrapper;
|
||||
let f;
|
||||
let store;
|
||||
|
||||
const findDiscardModal = () => wrapper.find({ ref: 'discardModal' });
|
||||
const findDiscardButton = () => wrapper.find({ ref: 'discardButton' });
|
||||
const findActionButton = () => wrapper.find({ ref: 'actionButton' });
|
||||
|
||||
beforeEach(() => {
|
||||
f = file('file');
|
||||
store = createStore();
|
||||
|
||||
wrapper = mount(EditorHeader, {
|
||||
store,
|
||||
localVue,
|
||||
sync: false,
|
||||
propsData: {
|
||||
activeFile: f,
|
||||
},
|
||||
});
|
||||
|
||||
jest.spyOn(wrapper.vm, 'stageChange').mockImplementation();
|
||||
jest.spyOn(wrapper.vm, 'unstageChange').mockImplementation();
|
||||
jest.spyOn(wrapper.vm, 'discardFileChanges').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders button to discard & stage', () => {
|
||||
expect(wrapper.vm.$el.querySelectorAll('.btn').length).toBe(2);
|
||||
});
|
||||
|
||||
describe('discard button', () => {
|
||||
let modal;
|
||||
|
||||
beforeEach(() => {
|
||||
modal = findDiscardModal();
|
||||
|
||||
jest.spyOn(modal.vm, 'show');
|
||||
|
||||
findDiscardButton().trigger('click');
|
||||
});
|
||||
|
||||
it('opens a dialog confirming discard', () => {
|
||||
expect(modal.vm.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls discardFileChanges if dialog result is confirmed', () => {
|
||||
modal.vm.$emit('ok');
|
||||
|
||||
expect(wrapper.vm.discardFileChanges).toHaveBeenCalledWith(f.path);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stage/unstage button', () => {
|
||||
it('unstages the file if it was already staged', () => {
|
||||
f.staged = true;
|
||||
|
||||
findActionButton().trigger('click');
|
||||
|
||||
expect(wrapper.vm.unstageChange).toHaveBeenCalledWith(f.path);
|
||||
});
|
||||
|
||||
it('stages the file if it was not staged', () => {
|
||||
findActionButton().trigger('click');
|
||||
|
||||
expect(wrapper.vm.stageChange).toHaveBeenCalledWith(f.path);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
|
||||
import SentryErrorStackTrace from '~/sentry_error_stack_trace/components/sentry_error_stack_trace.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('Sentry Error Stack Trace', () => {
|
||||
let actions;
|
||||
let getters;
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
function mountComponent({
|
||||
stubs = {
|
||||
stacktrace: Stacktrace,
|
||||
},
|
||||
} = {}) {
|
||||
wrapper = shallowMount(SentryErrorStackTrace, {
|
||||
localVue,
|
||||
stubs,
|
||||
store,
|
||||
propsData: {
|
||||
issueStackTracePath: '/stacktrace',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {
|
||||
startPollingStacktrace: () => {},
|
||||
};
|
||||
|
||||
getters = {
|
||||
stacktrace: () => [{ context: [1, 2], lineNo: 53, filename: 'index.js' }],
|
||||
};
|
||||
|
||||
const state = {
|
||||
stacktraceData: {},
|
||||
loadingStacktrace: true,
|
||||
};
|
||||
|
||||
store = new Vuex.Store({
|
||||
modules: {
|
||||
details: {
|
||||
namespaced: true,
|
||||
actions,
|
||||
getters,
|
||||
state,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
describe('loading', () => {
|
||||
it('should show spinner while loading', () => {
|
||||
mountComponent();
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stack trace', () => {
|
||||
it('should show stacktrace', () => {
|
||||
store.state.details.loadingStacktrace = false;
|
||||
mountComponent({ stubs: {} });
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not show stacktrace if it does not exist', () => {
|
||||
store.state.details.loadingStacktrace = false;
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.find(Stacktrace).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -29,23 +29,40 @@ describe('User Popover Component', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findUserStatus = () => wrapper.find('.js-user-status');
|
||||
const findTarget = () => document.querySelector('.js-user-link');
|
||||
|
||||
const createWrapper = (props = {}, options = {}) => {
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: findTarget(),
|
||||
...props,
|
||||
},
|
||||
sync: false,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
describe('Empty', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
target: document.querySelector('.js-user-link'),
|
||||
user: {
|
||||
name: null,
|
||||
username: null,
|
||||
location: null,
|
||||
bio: null,
|
||||
organization: null,
|
||||
status: null,
|
||||
createWrapper(
|
||||
{},
|
||||
{
|
||||
propsData: {
|
||||
target: findTarget(),
|
||||
user: {
|
||||
name: null,
|
||||
username: null,
|
||||
location: null,
|
||||
bio: null,
|
||||
organization: null,
|
||||
status: null,
|
||||
},
|
||||
},
|
||||
attachToDocument: true,
|
||||
},
|
||||
attachToDocument: true,
|
||||
sync: false,
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
it('should return skeleton loaders', () => {
|
||||
|
@ -55,13 +72,7 @@ describe('User Popover Component', () => {
|
|||
|
||||
describe('basic data', () => {
|
||||
it('should show basic fields', () => {
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper();
|
||||
|
||||
expect(wrapper.text()).toContain(DEFAULT_PROPS.user.name);
|
||||
expect(wrapper.text()).toContain(DEFAULT_PROPS.user.username);
|
||||
|
@ -77,64 +88,38 @@ describe('User Popover Component', () => {
|
|||
|
||||
describe('job data', () => {
|
||||
it('should show only bio if no organization is available', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.bio = 'Engineer';
|
||||
const user = { ...DEFAULT_PROPS.user, bio: 'Engineer' };
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...testProps,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(wrapper.text()).toContain('Engineer');
|
||||
});
|
||||
|
||||
it('should show only organization if no bio is available', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.organization = 'GitLab';
|
||||
const user = { ...DEFAULT_PROPS.user, organization: 'GitLab' };
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...testProps,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(wrapper.text()).toContain('GitLab');
|
||||
});
|
||||
|
||||
it('should display bio and organization in separate lines', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.bio = 'Engineer';
|
||||
testProps.user.organization = 'GitLab';
|
||||
const user = { ...DEFAULT_PROPS.user, bio: 'Engineer', organization: 'GitLab' };
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(wrapper.find('.js-bio').text()).toContain('Engineer');
|
||||
expect(wrapper.find('.js-organization').text()).toContain('GitLab');
|
||||
});
|
||||
|
||||
it('should not encode special characters in bio and organization', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.bio = 'Manager & Team Lead';
|
||||
testProps.user.organization = 'Me & my <funky> Company';
|
||||
const user = {
|
||||
...DEFAULT_PROPS.user,
|
||||
bio: 'Manager & Team Lead',
|
||||
organization: 'Me & my <funky> Company',
|
||||
};
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(wrapper.find('.js-bio').text()).toContain('Manager & Team Lead');
|
||||
expect(wrapper.find('.js-organization').text()).toContain('Me & my <funky> Company');
|
||||
|
@ -153,35 +138,41 @@ describe('User Popover Component', () => {
|
|||
|
||||
describe('status data', () => {
|
||||
it('should show only message', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.status = { message_html: 'Hello World' };
|
||||
const user = { ...DEFAULT_PROPS.user, status: { message_html: 'Hello World' } };
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(findUserStatus().exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('Hello World');
|
||||
});
|
||||
|
||||
it('should show message and emoji', () => {
|
||||
const testProps = Object.assign({}, DEFAULT_PROPS);
|
||||
testProps.user.status = { emoji: 'basketball_player', message_html: 'Hello World' };
|
||||
const user = {
|
||||
...DEFAULT_PROPS.user,
|
||||
status: { emoji: 'basketball_player', message_html: 'Hello World' },
|
||||
};
|
||||
|
||||
wrapper = shallowMount(UserPopover, {
|
||||
propsData: {
|
||||
...DEFAULT_PROPS,
|
||||
target: document.querySelector('.js-user-link'),
|
||||
status: { emoji: 'basketball_player', message_html: 'Hello World' },
|
||||
},
|
||||
sync: false,
|
||||
});
|
||||
createWrapper({ user });
|
||||
|
||||
expect(findUserStatus().exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('Hello World');
|
||||
expect(wrapper.html()).toContain('<gl-emoji data-name="basketball_player"');
|
||||
});
|
||||
|
||||
it('hides the div when status is null', () => {
|
||||
const user = { ...DEFAULT_PROPS.user, status: null };
|
||||
|
||||
createWrapper({ user });
|
||||
|
||||
expect(findUserStatus().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the div when status is empty', () => {
|
||||
const user = { ...DEFAULT_PROPS.user, status: { emoji: '', message_html: '' } };
|
||||
|
||||
createWrapper({ user });
|
||||
|
||||
expect(findUserStatus().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
|
||||
|
||||
describe('Issues Filtered Search Token Keys', () => {
|
||||
describe('get', () => {
|
||||
let tokenKeys;
|
||||
|
||||
beforeEach(() => {
|
||||
tokenKeys = IssuableFilteredSearchTokenKeys.get();
|
||||
});
|
||||
|
||||
it('should return tokenKeys', () => {
|
||||
expect(tokenKeys).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should return tokenKeys as an array', () => {
|
||||
expect(tokenKeys instanceof Array).toBe(true);
|
||||
});
|
||||
|
||||
it('should always return the same array', () => {
|
||||
const tokenKeys2 = IssuableFilteredSearchTokenKeys.get();
|
||||
|
||||
expect(tokenKeys).toEqual(tokenKeys2);
|
||||
});
|
||||
|
||||
it('should return assignee as a string', () => {
|
||||
const assignee = tokenKeys.find(tokenKey => tokenKey.key === 'assignee');
|
||||
|
||||
expect(assignee.type).toEqual('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeys', () => {
|
||||
it('should return keys', () => {
|
||||
const getKeys = IssuableFilteredSearchTokenKeys.getKeys();
|
||||
const keys = IssuableFilteredSearchTokenKeys.get().map(i => i.key);
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
expect(key).toEqual(getKeys[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConditions', () => {
|
||||
let conditions;
|
||||
|
||||
beforeEach(() => {
|
||||
conditions = IssuableFilteredSearchTokenKeys.getConditions();
|
||||
});
|
||||
|
||||
it('should return conditions', () => {
|
||||
expect(conditions).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should return conditions as an array', () => {
|
||||
expect(conditions instanceof Array).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByKey', () => {
|
||||
it('should return null when key not found', () => {
|
||||
const tokenKey = IssuableFilteredSearchTokenKeys.searchByKey('notakey');
|
||||
|
||||
expect(tokenKey).toBeNull();
|
||||
});
|
||||
|
||||
it('should return tokenKey when found by key', () => {
|
||||
const tokenKeys = IssuableFilteredSearchTokenKeys.get();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
|
||||
|
||||
expect(result).toEqual(tokenKeys[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchBySymbol', () => {
|
||||
it('should return null when symbol not found', () => {
|
||||
const tokenKey = IssuableFilteredSearchTokenKeys.searchBySymbol('notasymbol');
|
||||
|
||||
expect(tokenKey).toBeNull();
|
||||
});
|
||||
|
||||
it('should return tokenKey when found by symbol', () => {
|
||||
const tokenKeys = IssuableFilteredSearchTokenKeys.get();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
|
||||
|
||||
expect(result).toEqual(tokenKeys[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByKeyParam', () => {
|
||||
it('should return null when key param not found', () => {
|
||||
const tokenKey = IssuableFilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
|
||||
|
||||
expect(tokenKey).toBeNull();
|
||||
});
|
||||
|
||||
it('should return tokenKey when found by key param', () => {
|
||||
const tokenKeys = IssuableFilteredSearchTokenKeys.get();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchByKeyParam(
|
||||
`${tokenKeys[0].key}_${tokenKeys[0].param}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual(tokenKeys[0]);
|
||||
});
|
||||
|
||||
it('should return alternative tokenKey when found by key param', () => {
|
||||
const tokenKeys = IssuableFilteredSearchTokenKeys.getAlternatives();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchByKeyParam(
|
||||
`${tokenKeys[0].key}_${tokenKeys[0].param}`,
|
||||
);
|
||||
|
||||
expect(result).toEqual(tokenKeys[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByConditionUrl', () => {
|
||||
it('should return null when condition url not found', () => {
|
||||
const condition = IssuableFilteredSearchTokenKeys.searchByConditionUrl(null);
|
||||
|
||||
expect(condition).toBeNull();
|
||||
});
|
||||
|
||||
it('should return condition when found by url', () => {
|
||||
const conditions = IssuableFilteredSearchTokenKeys.getConditions();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
|
||||
|
||||
expect(result).toBe(conditions[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByConditionKeyValue', () => {
|
||||
it('should return null when condition tokenKey and value not found', () => {
|
||||
const condition = IssuableFilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
|
||||
|
||||
expect(condition).toBeNull();
|
||||
});
|
||||
|
||||
it('should return condition when found by tokenKey and value', () => {
|
||||
const conditions = IssuableFilteredSearchTokenKeys.getConditions();
|
||||
const result = IssuableFilteredSearchTokenKeys.searchByConditionKeyValue(
|
||||
conditions[0].tokenKey,
|
||||
conditions[0].value,
|
||||
);
|
||||
|
||||
expect(result).toEqual(conditions[0]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -348,6 +348,8 @@ describe('IDE store merge request actions', () => {
|
|||
let testMergeRequest;
|
||||
let testMergeRequestChanges;
|
||||
|
||||
const mockGetters = { findBranch: () => ({ commit: { id: 'abcd2322' } }) };
|
||||
|
||||
beforeEach(() => {
|
||||
testMergeRequest = {
|
||||
source_branch: 'abcbranch',
|
||||
|
@ -406,8 +408,8 @@ describe('IDE store merge request actions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('dispatch actions for merge request data', done => {
|
||||
openMergeRequest(store, mr)
|
||||
it('dispatches actions for merge request data', done => {
|
||||
openMergeRequest({ state: store.state, dispatch: store.dispatch, getters: mockGetters }, mr)
|
||||
.then(() => {
|
||||
expect(store.dispatch.calls.allArgs()).toEqual([
|
||||
['getMergeRequestData', mr],
|
||||
|
@ -424,6 +426,7 @@ describe('IDE store merge request actions', () => {
|
|||
{
|
||||
projectId: mr.projectId,
|
||||
branchId: testMergeRequest.source_branch,
|
||||
ref: 'abcd2322',
|
||||
},
|
||||
],
|
||||
['getMergeRequestVersions', mr],
|
||||
|
@ -449,7 +452,7 @@ describe('IDE store merge request actions', () => {
|
|||
{ new_path: 'bar', path: 'bar' },
|
||||
];
|
||||
|
||||
openMergeRequest(store, mr)
|
||||
openMergeRequest({ state: store.state, dispatch: store.dispatch, getters: mockGetters }, mr)
|
||||
.then(() => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
'updateActivityBarView',
|
||||
|
|
|
@ -285,16 +285,21 @@ describe('IDE store project actions', () => {
|
|||
describe('loadBranch', () => {
|
||||
const projectId = 'abc/def';
|
||||
const branchId = '123-lorem';
|
||||
const ref = 'abcd2322';
|
||||
|
||||
it('fetches branch data', done => {
|
||||
const mockGetters = { findBranch: () => ({ commit: { id: ref } }) };
|
||||
spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
|
||||
|
||||
loadBranch(store, { projectId, branchId })
|
||||
loadBranch(
|
||||
{ getters: mockGetters, state: store.state, dispatch: store.dispatch },
|
||||
{ projectId, branchId },
|
||||
)
|
||||
.then(() => {
|
||||
expect(store.dispatch.calls.allArgs()).toEqual([
|
||||
['getBranchData', { projectId, branchId }],
|
||||
['getMergeRequestsForBranch', { projectId, branchId }],
|
||||
['getFiles', { projectId, branchId }],
|
||||
['getFiles', { projectId, branchId, ref }],
|
||||
]);
|
||||
})
|
||||
.then(done)
|
||||
|
|
|
@ -17,6 +17,7 @@ describe('Multi-file store tree actions', () => {
|
|||
projectId: 'abcproject',
|
||||
branch: 'master',
|
||||
branchId: 'master',
|
||||
ref: '12345678',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -29,14 +30,6 @@ describe('Multi-file store tree actions', () => {
|
|||
store.state.currentBranchId = 'master';
|
||||
store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '12345678',
|
||||
commit: {
|
||||
id: '12345678',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ module FilteredSearchHelpers
|
|||
filtered_search.send_keys(:enter)
|
||||
end
|
||||
|
||||
def expect_filtered_search_dropdown_results(filter_dropdown, count)
|
||||
expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: count)
|
||||
end
|
||||
|
||||
def expect_issues_list_count(open_count, closed_count = 0)
|
||||
all_count = open_count + closed_count
|
||||
|
||||
|
|
|
@ -130,4 +130,26 @@ describe 'projects/issues/show' do
|
|||
expect(rendered).to have_selector('.status-box-open:not(.hidden)', text: 'Open')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the issue is related to a sentry error' do
|
||||
it 'renders a stack trace' do
|
||||
sentry_issue = double(:sentry_issue, sentry_issue_identifier: '1066622')
|
||||
allow(issue).to receive(:sentry_issue).and_return(sentry_issue)
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector(
|
||||
"#js-sentry-error-stack-trace"\
|
||||
"[data-issue-stack-trace-path="\
|
||||
"\"/#{project.full_path}/-/error_tracking/1066622/stack_trace.json\"]"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the issue is not related to a sentry error' do
|
||||
it 'does not render a stack trace' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_selector('#js-sentry-error-stack-trace')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue