diff options
39 files changed, 795 insertions, 1586 deletions
diff --git a/.gitignore b/.gitignore index d43b1908dd3..e1dc80e79b0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .bundle .chef .directory +.eslintcache /.envrc eslint-report.html /.gitlab_shell_secret diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index 1e564d8ebad..7050d44cd88 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -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. diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index f5a67299dde..4996ce3d342 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -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>', }, diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue index 6b2ef34c960..3398cd091ba 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue @@ -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> diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index 6790c0fbdaa..806ec38430c 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -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', { diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index 20887e7d0ac..52bf9becd0f 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -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(() => { diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js index 72cd099c5a5..ba85194b910 100644 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -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 }, }); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 52674107df2..96c4741fc2e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -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(); diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 28a136a5fa5..75df80a0f6c 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -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 diff --git a/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue b/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue new file mode 100644 index 00000000000..c90478db620 --- /dev/null +++ b/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue @@ -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> diff --git a/app/assets/javascripts/sentry_error_stack_trace/index.js b/app/assets/javascripts/sentry_error_stack_trace/index.js new file mode 100644 index 00000000000..9b24ddc335d --- /dev/null +++ b/app/assets/javascripts/sentry_error_stack_trace/index.js @@ -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 }, + }), + }); + } +} diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index 4a72cca5f02..37e3643bf6c 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -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> diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 17f6fe95f10..9062f2097b8 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -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 } } diff --git a/changelogs/unreleased/36545-frontend-add-stack-trace-component-below-details-when-issue-is-crea.yml b/changelogs/unreleased/36545-frontend-add-stack-trace-component-below-details-when-issue-is-crea.yml new file mode 100644 index 00000000000..0489c087866 --- /dev/null +++ b/changelogs/unreleased/36545-frontend-add-stack-trace-component-below-details-when-issue-is-crea.yml @@ -0,0 +1,5 @@ +--- +title: Add stacktrace to issue created from the sentry error detail page +merge_request: 21438 +author: +type: added diff --git a/changelogs/unreleased/himkp-27242.yml b/changelogs/unreleased/himkp-27242.yml new file mode 100644 index 00000000000..a9c7d69dd26 --- /dev/null +++ b/changelogs/unreleased/himkp-27242.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix issue: Discard button in Web IDE does nothing' +merge_request: 21902 +author: +type: fixed diff --git a/changelogs/unreleased/polish-user-popover.yml b/changelogs/unreleased/polish-user-popover.yml new file mode 100644 index 00000000000..0531fc0a5ec --- /dev/null +++ b/changelogs/unreleased/polish-user-popover.yml @@ -0,0 +1,5 @@ +--- +title: Remove extra whitespace in user popover +merge_request: 19938 +author: +type: fixed diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md index 513c18c095f..a77584c0485 100644 --- a/doc/user/project/clusters/add_remove_clusters.md +++ b/doc/user/project/clusters/add_remove_clusters.md @@ -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) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71ab65e84f6..823c7700e93 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/package.json b/package.json index 87bbc0caf4f..f15da3b985e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index e1177bedd2d..2d7f5822996 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index bd22eb1056b..6567bbcf8a2 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/dropdown_base_spec.rb b/spec/features/issues/filtered_search/dropdown_base_spec.rb new file mode 100644 index 00000000000..0a8d768fe49 --- /dev/null +++ b/spec/features/issues/filtered_search/dropdown_base_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index 7ec3d215fb1..324f39cbd2c 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -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:') - - 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(':') + input_filtered_search('my-reaction:', submit: false, extra_space: false) - 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 diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index bb57d69148b..5994f3a7902 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index f7f9f0de4db..45112b01eac 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -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') - 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 + filtered_search.set('label:') - expect(filter_dropdown).to have_selector('.filter-dropdown-item', count: 1) + expect_filtered_search_dropdown_results(filter_dropdown, 1) end end end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 5272a970a60..2f18aa8abaa 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/dropdown_release_spec.rb b/spec/features/issues/filtered_search/dropdown_release_spec.rb index eea7f2d7848..b9cce5c6998 100644 --- a/spec/features/issues/filtered_search/dropdown_release_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_release_spec.rb @@ -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 diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 8b5e7934ec1..12e010e293a 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -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)}/" diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index f8035ef4b85..d1e976c3bca 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -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) + expect(page).to have_css('#js-dropdown-author', 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 diff --git a/spec/features/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb index d6a406f4f44..7577df3bc7d 100644 --- a/spec/features/issues/rss_spec.rb +++ b/spec/features/issues/rss_spec.rb @@ -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 diff --git a/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js new file mode 100644 index 00000000000..ad69984fd35 --- /dev/null +++ b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js @@ -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); + }); + }); +}); diff --git a/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js b/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js new file mode 100644 index 00000000000..fb50b31a3da --- /dev/null +++ b/spec/frontend/sentry_error_stack_trace/components/sentry_error_stack_trace_spec.js @@ -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); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index f2e743cc1f6..440ad1ca40f 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -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'; - - wrapper = shallowMount(UserPopover, { - propsData: { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }, - sync: false, - }); + const user = { ...DEFAULT_PROPS.user, bio: 'Engineer', organization: 'GitLab' }; + + 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'; - - wrapper = shallowMount(UserPopover, { - propsData: { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - }, - sync: false, - }); + const user = { + ...DEFAULT_PROPS.user, + bio: 'Manager & Team Lead', + organization: 'Me & my <funky> Company', + }; + + 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' }; - - wrapper = shallowMount(UserPopover, { - propsData: { - ...DEFAULT_PROPS, - target: document.querySelector('.js-user-link'), - status: { emoji: 'basketball_player', message_html: 'Hello World' }, - }, - sync: false, - }); + const user = { + ...DEFAULT_PROPS.user, + status: { emoji: 'basketball_player', message_html: 'Hello World' }, + }; + + 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); + }); }); }); diff --git a/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js new file mode 100644 index 00000000000..e33a6c002e5 --- /dev/null +++ b/spec/javascripts/filtered_search/issues_filtered_search_token_keys_spec.js @@ -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]); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index a8894c644be..ca8f33407fd 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -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', diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index bcc7b5d5e46..6a7116d87f2 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -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) diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index e2d8cc195ae..be350b6f6cc 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -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', - }, - }, - }, }; }); diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb index 5dc87c36931..350c8a29e87 100644 --- a/spec/support/helpers/filtered_search_helpers.rb +++ b/spec/support/helpers/filtered_search_helpers.rb @@ -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 diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb index d34b1735445..add4b44e9b6 100644 --- a/spec/views/projects/issues/show.html.haml_spec.rb +++ b/spec/views/projects/issues/show.html.haml_spec.rb @@ -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 |