Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-12-20 21:08:00 +00:00
parent 855bf0533b
commit be59dd1d43
39 changed files with 792 additions and 1583 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@
.bundle
.chef
.directory
.eslintcache
/.envrc
eslint-report.html
/.gitlab_shell_secret

View file

@ -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.

View file

@ -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>',
},

View file

@ -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>

View file

@ -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', {

View file

@ -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(() => {

View file

@ -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 },
});

View file

@ -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();

View file

@ -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

View file

@ -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>

View 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 },
}),
});
}
}

View file

@ -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>

View file

@ -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 } }

View file

@ -0,0 +1,5 @@
---
title: Add stacktrace to issue created from the sentry error detail page
merge_request: 21438
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: 'Fix issue: Discard button in Web IDE does nothing'
merge_request: 21902
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Remove extra whitespace in user popover
merge_request: 19938
author:
type: fixed

View file

@ -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)

View file

@ -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 ""

View file

@ -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",

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)}/"

View file

@ -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

View file

@ -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

View file

@ -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);
});
});
});

View file

@ -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);
});
});
});

View file

@ -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);
});
});
});

View file

@ -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]);
});
});
});

View file

@ -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',

View file

@ -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)

View file

@ -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',
},
},
},
};
});

View file

@ -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

View file

@ -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