Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-02 00:07:18 +00:00
parent cc77bdd6f5
commit a0e1fa9aad
50 changed files with 617 additions and 218 deletions

View File

@ -225,7 +225,7 @@ export default {
v-model="searchText"
role="searchbox"
class="gl-z-index-1"
data-testid="global_search_input"
data-testid="global-search-input"
autocomplete="off"
:placeholder="$options.i18n.SEARCH_GITLAB"
:aria-activedescendant="currentFocusedId"

View File

@ -518,7 +518,7 @@ export default {
<gl-dropdown-item
v-gl-modal="$options.deleteModalId"
variant="danger"
data-testid="delete_issue_button"
data-testid="delete-issue-button"
@click="track('click_dropdown')"
>
{{ deleteButtonText }}

View File

@ -1,11 +1,9 @@
<script>
import { GlKeysetPagination } from '@gitlab/ui';
import ImageListRow from './image_list_row.vue';
export default {
name: 'ImageList',
components: {
GlKeysetPagination,
ImageListRow,
},
props: {
@ -18,10 +16,6 @@ export default {
default: false,
required: false,
},
pageInfo: {
type: Object,
required: true,
},
expirationPolicy: {
type: Object,
default: () => ({}),
@ -41,13 +35,5 @@ export default {
:expiration-policy="expirationPolicy"
@delete="$emit('delete', $event)"
/>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
v-bind="pageInfo"
class="gl-mt-3"
@prev="$emit('prev-page')"
@next="$emit('next-page')"
/>
</div>
</div>
</template>

View File

@ -14,6 +14,7 @@ import { createAlert } from '~/alert';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import { fetchPolicies } from '~/lib/graphql';
import Tracking from '~/tracking';
import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import DeleteImage from '../components/delete_image.vue';
@ -33,6 +34,7 @@ import {
SETTINGS_TEXT,
} from '../constants/index';
import getContainerRepositoriesDetails from '../graphql/queries/get_container_repositories_details.query.graphql';
import { getPageParams, getNextPageParams, getPreviousPageParams } from '../utils';
export default {
name: 'RegistryListPage',
@ -62,6 +64,7 @@ export default {
GlSkeletonLoader,
RegistryHeader,
DeleteImage,
PersistedPagination,
PersistedSearch,
},
directives: {
@ -198,25 +201,18 @@ export default {
this.deleteAlertType = null;
this.itemToDelete = {};
},
async fetchNextPage() {
this.pageParams = {
after: this.pageInfo?.endCursor,
first: GRAPHQL_PAGE_SIZE,
};
fetchNextPage() {
this.pageParams = getNextPageParams(this.pageInfo?.endCursor);
},
async fetchPreviousPage() {
this.pageParams = {
first: null,
before: this.pageInfo?.startCursor,
last: GRAPHQL_PAGE_SIZE,
};
fetchPreviousPage() {
this.pageParams = getPreviousPageParams(this.pageInfo?.startCursor);
},
startDelete() {
this.track('confirm_delete');
this.mutationLoading = true;
},
handleSearchUpdate({ sort, filters }) {
this.pageParams = {};
handleSearchUpdate({ sort, filters, pageInfo }) {
this.pageParams = getPageParams(pageInfo);
this.sorting = sort;
const search = filters.find((i) => i.type === FILTERED_SEARCH_TERM);
@ -322,11 +318,8 @@ export default {
v-if="images.length"
:images="images"
:metadata-loading="$apollo.queries.additionalDetails.loading"
:page-info="pageInfo"
:expiration-policy="config.expirationPolicy"
@delete="deleteImage"
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
/>
<gl-empty-state
@ -346,6 +339,15 @@ export default {
</template>
</template>
<div class="gl-display-flex gl-justify-content-center">
<persisted-pagination
class="gl-mt-3"
:pagination="pageInfo"
@prev="fetchPreviousPage"
@next="fetchNextPage"
/>
</div>
<delete-image
:id="itemToDelete.id"
@start="startDelete"

View File

@ -1,4 +1,5 @@
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
import { GRAPHQL_PAGE_SIZE } from './constants/index';
export const getImageName = (image = {}) => {
return image.name || image.project?.path;
@ -10,3 +11,26 @@ export const timeTilRun = (time) => {
const difference = calculateRemainingMilliseconds(time);
return approximateDuration(difference / 1000);
};
export const getNextPageParams = (cursor) => ({
after: cursor,
first: GRAPHQL_PAGE_SIZE,
});
export const getPreviousPageParams = (cursor) => ({
first: null,
before: cursor,
last: GRAPHQL_PAGE_SIZE,
});
export const getPageParams = (pageInfo = {}) => {
if (pageInfo.before) {
return getPreviousPageParams(pageInfo.before);
}
if (pageInfo.after) {
return getNextPageParams(pageInfo.after);
}
return {};
};

View File

@ -0,0 +1,56 @@
<script>
import { GlKeysetPagination } from '@gitlab/ui';
import UrlSync from '~/vue_shared/components/url_sync.vue';
export default {
name: 'PersistedPagination',
components: {
GlKeysetPagination,
UrlSync,
},
inheritAttrs: false,
props: {
pagination: {
type: Object,
default: () => ({}),
required: false,
},
},
computed: {
attrs() {
return {
...this.pagination,
...this.$attrs,
};
},
},
methods: {
onPrev(updateQuery) {
updateQuery({
before: this.pagination?.startCursor,
after: null,
});
this.$emit('prev');
},
onNext(updateQuery) {
updateQuery({
after: this.pagination?.endCursor,
before: null,
});
this.$emit('next');
},
},
};
</script>
<template>
<url-sync>
<template #default="{ updateQuery }">
<gl-keyset-pagination
v-bind="attrs"
@prev="onPrev(updateQuery)"
@next="onNext(updateQuery)"
/>
</template>
</url-sync>
</template>

View File

@ -1,7 +1,11 @@
<script>
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { extractFilterAndSorting, getQueryParams } from '~/packages_and_registries/shared/utils';
import {
extractFilterAndSorting,
extractPageInfo,
getQueryParams,
} from '~/packages_and_registries/shared/utils';
export default {
components: { RegistrySearch, UrlSync },
@ -31,6 +35,7 @@ export default {
orderBy: this.defaultOrder,
sort: this.defaultSort,
},
pageInfo: {},
mountRegistrySearch: false,
};
},
@ -40,27 +45,49 @@ export default {
return `${cleanOrderBy}_${this.sorting?.sort}`.toUpperCase();
},
},
watch: {
$route(newValue, oldValue) {
if (newValue.fullPath !== oldValue.fullPath) {
this.updateDataFromUrl();
this.emitUpdate();
}
},
},
mounted() {
const queryParams = getQueryParams(window.document.location.search);
const { sorting, filters } = extractFilterAndSorting(queryParams);
this.updateSorting(sorting);
this.updateFilters(filters);
this.updateDataFromUrl();
this.mountRegistrySearch = true;
this.emitUpdate();
},
methods: {
updateDataFromUrl() {
const queryParams = getQueryParams(window.location.search);
const { sorting, filters } = extractFilterAndSorting(queryParams);
const pageInfo = extractPageInfo(queryParams);
this.updateSorting(sorting);
this.updateFilters(filters);
this.updatePageInfo(pageInfo);
},
updateFilters(newValue) {
this.updatePageInfo({});
this.filters = newValue;
},
updateSorting(newValue) {
this.updatePageInfo({});
this.sorting = { ...this.sorting, ...newValue };
},
updatePageInfo(newValue) {
this.pageInfo = newValue;
},
updateSortingAndEmitUpdate(newValue) {
this.updateSorting(newValue);
this.emitUpdate();
},
emitUpdate() {
this.$emit('update', { sort: this.parsedSorting, filters: this.filters });
this.$emit('update', {
sort: this.parsedSorting,
filters: this.filters,
pageInfo: this.pageInfo,
});
},
},
};

View File

@ -30,6 +30,14 @@ export const extractFilterAndSorting = (queryObject) => {
return { filters, sorting };
};
export const extractPageInfo = (queryObject) => {
const { before, after } = queryObject;
return {
before,
after,
};
};
export const beautifyPath = (path) => (path ? path.split('/').join(' / ') : '');
export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGroup = false) => {

View File

@ -60,7 +60,13 @@ export default {
methods: {
generateQueryData({ sorting = {}, filter = [] } = {}) {
// Ensure that we clean up the query when we remove a token from the search
const result = { ...this.baselineQueryStringFilters, ...sorting, search: [] };
const result = {
...this.baselineQueryStringFilters,
...sorting,
search: [],
after: null,
before: null,
};
filter.forEach((f) => {
if (f.type === FILTERED_SEARCH_TERM) {

View File

@ -92,7 +92,7 @@
- if header_link?(:todos)
= nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do
= link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos js-prefetch-document',
data: { testid: 'todos_shortcut_button', toggle: 'tooltip', placement: 'bottom',
data: { testid: 'todos-shortcut-button', toggle: 'tooltip', placement: 'bottom',
track_label: 'main_navigation',
track_action: 'click_to_do_link',
track_property: 'navigation_top',

View File

@ -483,8 +483,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
#
# Service Desk
#
get '/service_desk' => 'service_desk#show', as: :service_desk # rubocop:todo Cop/PutProjectRoutesUnderScope
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh # rubocop:todo Cop/PutProjectRoutesUnderScope
get '/service_desk' => 'service_desk#show' # rubocop:todo Cop/PutProjectRoutesUnderScope
put '/service_desk' => 'service_desk#update' # rubocop:todo Cop/PutProjectRoutesUnderScope
#
# Templates

View File

@ -22,7 +22,8 @@ architecture.
| OS Version | First supported GitLab version | Arch | Install Documentation | OS EOL | Details |
| ------------------------------------------------------------ | ------------------------------ | --------------- | :----------------------------------------------------------: | ---------- | ------------------------------------------------------------ |
| AlmaLinux 8 | GitLab CE / GitLab EE 14.5.0 | x86_64, aarch64 | [AlmaLinux Install Documentation](https://about.gitlab.com/install/#almalinux-8) | 2029 | <https://almalinux.org/> |
| AlmaLinux 8 | GitLab CE / GitLab EE 14.5.0 | x86_64, aarch64 | [AlmaLinux Install Documentation](https://about.gitlab.com/install/#almalinux) | 2029 | <https://almalinux.org/> |
| AlmaLinux 9 | GitLab CE / GitLab EE 16.0.0 | x86_64, aarch64 | [AlmaLinux Install Documentation](https://about.gitlab.com/install/#almalinux) | 2032 | <https://almalinux.org/> |
| CentOS 7 | GitLab CE / GitLab EE 7.10.0 | x86_64 | [CentOS Install Documentation](https://about.gitlab.com/install/#centos-7) | June 2024 | <https://wiki.centos.org/About/Product> |
| Debian 10 | GitLab CE / GitLab EE 12.2.0 | amd64, arm64 | [Debian Install Documentation](https://about.gitlab.com/install/#debian) | 2024 | <https://wiki.debian.org/LTS> |
| Debian 11 | GitLab CE / GitLab EE 14.6.0 | amd64, arm64 | [Debian Install Documentation](https://about.gitlab.com/install/#debian) | 2026 | <https://wiki.debian.org/LTS> |

View File

@ -105,7 +105,14 @@ Gather data on the state of the Sidekiq workers with the following Ruby script.
If the performance issue is intermittent:
- Run this in a cron job every five minutes. Write the files to a location with enough space: allow for 500 KB per file.
- Run this in a cron job every five minutes. Write the files to a location with enough space: allow for at least 500 KB per file.
```shell
cat > /etc/cron.d/sidekiqcheck <<EOF
*/5 * * * * root /opt/gitlab/bin/gitlab-rails runner /var/opt/gitlab/sidekiqcheck.rb > /tmp/sidekiqcheck_$(date '+\%Y\%m\%d-\%H:\%M').out
EOF
```
- Refer back to the data to see what went wrong.
1. Analyze the output. The following commands assume that you have a directory of output files.

View File

@ -92,6 +92,7 @@ as it can cause the pipeline to behave unexpectedly.
| `CI_PIPELINE_TRIGGERED` | all | all | `true` if the job was [triggered](../triggers/index.md). |
| `CI_PIPELINE_URL` | 11.1 | 0.5 | The URL for the pipeline details. |
| `CI_PIPELINE_CREATED_AT` | 13.10 | all | The UTC datetime when the pipeline was created, in [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. |
| `CI_PIPELINE_NAME` | 16.3 | all | The pipeline name defined in [`workflow:name`](../yaml/index.md#workflowname) |
| `CI_PROJECT_DIR` | all | all | The full path the repository is cloned to, and where the job runs from. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see the [Advanced GitLab Runner configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section). |
| `CI_PROJECT_ID` | all | all | The ID of the current project. This ID is unique across all projects on the GitLab instance. |
| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project. For example if the project URL is `gitlab.example.com/group-name/project-1`, `CI_PROJECT_NAME` is `project-1`. |

View File

@ -544,7 +544,7 @@ To upgrade GitLab that was [installed using Docker Engine](#install-gitlab-using
On the first run, GitLab will reconfigure and upgrade itself.
Refer to the GitLab [Upgrade recommendations](../policy/maintenance.md#upgrade-recommendations)
when upgrading between major versions.
when upgrading between versions.
### Upgrade GitLab using Docker compose

View File

@ -16,6 +16,7 @@ module Gitlab
variables.append(key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s)
variables.append(key: 'CI_PIPELINE_SOURCE', value: pipeline.source.to_s)
variables.append(key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at&.iso8601)
variables.append(key: 'CI_PIPELINE_NAME', value: pipeline.name)
variables.concat(predefined_commit_variables) if pipeline.sha.present?
variables.concat(predefined_commit_tag_variables) if pipeline.tag?

View File

@ -12,7 +12,7 @@ module QA
end
view 'app/assets/javascripts/admin/users/components/users_table.vue' do
element :user_row_content
element 'user-row-content'
end
def search_user(username)
@ -24,13 +24,13 @@ module QA
end
def click_user(username)
within_element(:user_row_content, text: username) do
within_element('user-row-content', text: username) do
click_link(username)
end
end
def has_username?(username)
has_element?(:user_row_content, text: username, wait: 1)
has_element?('user-row-content', text: username, wait: 1)
end
end
end

View File

@ -10,8 +10,8 @@ module QA
super
base.view 'app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue' do
element :confirm_ok_button
element :confirmation_modal
element 'confirm-ok-button'
element 'confirmation-modal'
end
base.view 'app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue' do
@ -36,7 +36,7 @@ module QA
end
def click_confirmation_ok_button
click_element(:confirm_ok_button)
click_element('confirm-ok-button')
end
# Click the confirmation button if the confirmation modal is present
@ -48,9 +48,9 @@ module QA
# to skip the loading check otherwise it will time out.
#
# [1]: https://gitlab.com/gitlab-org/gitlab/-/blob/4a99af809b86047ce3c8985e6582748bbd23fc84/qa/qa/page/component/members/members_table.rb#L54
return unless has_element?(:confirmation_modal, skip_finished_loading_check: true)
return unless has_element?('confirmation-modal', skip_finished_loading_check: true)
click_element(:confirm_ok_button, skip_finished_loading_check: true)
click_element('confirm-ok-button', skip_finished_loading_check: true)
end
end
end

View File

@ -11,7 +11,7 @@ module QA
super
base.view 'app/assets/javascripts/issues/show/components/title.vue' do
element :issue_title, required: true
element 'issue-title', required: true
end
end
end

View File

@ -31,7 +31,7 @@ module QA
end
base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents.vue' do
element :labels_select_dropdown_contents
element 'labels-select-dropdown-contents'
end
base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_value.vue' do
@ -49,7 +49,7 @@ module QA
end
base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do
element :edit_button
element 'edit-button'
end
base.view 'app/helpers/dropdowns_helper.rb' do
@ -59,7 +59,7 @@ module QA
def assign_milestone(milestone)
wait_milestone_block_finish_loading do
click_element(:edit_button)
click_element('edit-button')
click_on(milestone.title)
end
@ -134,17 +134,17 @@ module QA
def select_labels(labels)
within_element(:labels_block) do
click_element(:edit_button)
click_element('edit-button')
labels.each do |label|
within_element(:labels_select_dropdown_contents) do
within_element('labels-select-dropdown-contents') do
fill_element(:dropdown_input_field, label)
click_button(text: label)
end
end
end
click_element(:issue_title) # to blur dropdown
click_element('issue-title') # to blur dropdown
end
def toggle_more_assignees_link

View File

@ -171,7 +171,7 @@ module QA
def select_filter_with_text(text)
retry_on_exception do
click_element(:issue_title)
click_element('issue-title')
click_element :discussion_preferences_dropdown
find_element(:filter_menu_item, text: text).click

View File

@ -10,7 +10,7 @@ module QA
super
base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue' do
element :rich_text_promo_popover
element 'rich-text-promo-popover'
end
base.view 'app/views/shared/_broadcast_message.html.haml' do
@ -19,12 +19,12 @@ module QA
end
def close_rich_text_promo_popover_if_present
return unless has_element?(:rich_text_promo_popover, wait: 0)
return unless has_element?('rich-text-promo-popover', wait: 0)
within_element(:rich_text_promo_popover) do
click_element(:close_button)
within_element('rich-text-promo-popover') do
click_element('close-button')
end
has_no_element?(:rich_text_promo_popover)
has_no_element?('rich-text-promo-popover')
end
end
end

View File

@ -40,19 +40,14 @@ module QA
end
def matches?(line)
!!(line =~ /["']#{name}['"]|["']#{convert_to_kebabcase(name)}['"]|#{expression}/)
!!(line =~ /["']#{name}['"]|#{expression}/)
end
private
def convert_to_kebabcase(text)
text.to_s.tr('_', '-')
end
def qa_selector
[
%([data-testid="#{name}"]#{additional_selectors}),
%([data-testid="#{convert_to_kebabcase(name)}"]#{additional_selectors}),
%([data-qa-selector="#{name}"]#{additional_selectors})
].join(',')
end

View File

@ -8,7 +8,7 @@ module QA
element :import_table
element :import_item
element :import_status_indicator
element :filter_groups
element 'filter-groups'
end
view "app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue" do
@ -20,7 +20,7 @@ module QA
end
view "app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue" do
element :import_group_button
element 'import-group-button'
end
def filter_group(source_group_name)
@ -44,7 +44,7 @@ module QA
click_element(:target_group_dropdown_item, group_name: target_group_name)
retry_until(message: "Triggering import") do
click_element(:import_group_button)
click_element('import-group-button')
# Make sure import started before waiting for completion
has_no_element?(:import_status_indicator, text: "Not started", wait: 1)
end

View File

@ -5,11 +5,11 @@ module QA
module Group
class DependencyProxy < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue' do
element :proxy_count
element 'proxy-count'
end
def has_blob_count?(blob_text)
has_element?(:proxy_count, text: blob_text)
has_element?('proxy-count', text: blob_text)
end
end
end

View File

@ -23,7 +23,7 @@ module QA
end
view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents.vue' do
element :labels_select_dropdown_contents
element 'labels-select-dropdown-contents'
end
view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do

View File

@ -20,7 +20,7 @@ module QA
end
view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do
element :user_dropdown, required: !Runtime::Env.phone_layout?
element 'user-dropdown', required: !Runtime::Env.phone_layout?
element :user_avatar_content, required: !Runtime::Env.phone_layout?
element :sign_out_link
element :edit_profile_link
@ -31,26 +31,26 @@ module QA
end
view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do
element :super_sidebar_search_button
element :stop_impersonation_btn
element :issues_shortcut_button, required: !Runtime::Env.phone_layout?
element :merge_requests_shortcut_button, required: !Runtime::Env.phone_layout?
element :todos_shortcut_button, required: !Runtime::Env.phone_layout?
element 'super-sidebar-search-button'
element 'stop-impersonation-btn'
element 'issues-shortcut-button', required: !Runtime::Env.phone_layout?
element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout?
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
end
view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do
element :global_search_input
element 'global-search-input'
end
else
view 'app/views/layouts/header/_default.html.haml' do
element :navbar, required: true
element :canary_badge_link
element :user_avatar_content, required: !Runtime::Env.phone_layout?
element :user_dropdown, required: !Runtime::Env.phone_layout?
element :stop_impersonation_btn
element :issues_shortcut_button, required: !Runtime::Env.phone_layout?
element :merge_requests_shortcut_button, required: !Runtime::Env.phone_layout?
element :todos_shortcut_button, required: !Runtime::Env.phone_layout?
element 'user-dropdown', required: !Runtime::Env.phone_layout?
element 'stop-impersonation-btn'
element 'issues-shortcut-button', required: !Runtime::Env.phone_layout?
element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout?
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
end
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
@ -65,7 +65,7 @@ module QA
end
view 'app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue' do
element :menu_subview
element 'menu-subview'
end
view 'lib/gitlab/nav/top_nav_menu_item.rb' do
@ -84,11 +84,11 @@ module QA
end
view 'app/assets/javascripts/header_search/components/app.vue' do
element :global_search_input
element 'global-search-input'
end
view 'app/views/layouts/header/_new_dropdown.html.haml' do
element :new_menu_toggle
element 'new-menu-toggle'
end
view 'app/helpers/nav/new_dropdown_helper.rb' do
@ -140,17 +140,17 @@ module QA
end
def go_to_create_project
click_element(:new_menu_toggle)
click_element('new-menu-toggle')
click_element(:global_new_project_link)
end
def go_to_create_group
click_element(:new_menu_toggle)
click_element('new-menu-toggle')
click_element(:global_new_group_link)
end
def go_to_create_snippet
click_element(:new_menu_toggle)
click_element('new-menu-toggle')
click_element(:global_new_snippet_link)
end
@ -167,7 +167,7 @@ module QA
# @param [Symbol] the name of the element (e.g: `:issues_shortcut button`)
# @example:
# Menu.perform do |menu|
# menu.go_to_page_by_shortcut(:issues_shortcut_button) #=> Go to Issues page using shortcut button
# menu.go_to_page_by_shortcut('issues-shortcut-button') #=> Go to Issues page using shortcut button
# end
def go_to_page_by_shortcut(button)
within_top_menu do
@ -243,8 +243,8 @@ module QA
end
def search_for(term)
click_element(Runtime::Env.super_sidebar_enabled? ? :super_sidebar_search_button : :search_box)
fill_element(:global_search_input, "#{term}\n")
click_element(Runtime::Env.super_sidebar_enabled? ? 'super-sidebar-search-button' : :search_box)
fill_element('global-search-input', "#{term}\n")
end
def has_personal_area?(wait: Capybara.default_max_wait_time)
@ -265,7 +265,7 @@ module QA
end
def click_stop_impersonation_link
click_element(:stop_impersonation_btn)
click_element('stop-impersonation-btn')
end
# To verify whether the user has been directed to a canary web node
@ -308,20 +308,20 @@ module QA
within_top_menu do
click_element :user_avatar_content unless has_element?(:user_profile_link, wait: 1)
within_element(:user_dropdown, &block)
within_element('user-dropdown', &block)
end
end
def within_groups_menu(&block)
go_to_menu_dropdown_option(:groups_dropdown)
within_element(:menu_subview, &block)
within_element('menu-subview', &block)
end
def within_projects_menu(&block)
go_to_menu_dropdown_option(:projects_dropdown)
within_element(:menu_subview, &block)
within_element('menu-subview', &block)
end
def click_admin_area

View File

@ -21,7 +21,7 @@ module QA
view 'app/assets/javascripts/issues/show/components/header_actions.vue' do
element 'toggle-issue-state-button'
element 'desktop-dropdown'
element :delete_issue_button
element 'delete-issue-button'
end
view 'app/assets/javascripts/related_issues/components/add_issuable_form.vue' do
@ -33,7 +33,7 @@ module QA
end
view 'app/assets/javascripts/related_issues/components/related_issues_block.vue' do
element :related_issues_plus_button
element 'related-issues-plus-button'
end
view 'app/assets/javascripts/related_issues/components/related_issues_list.vue' do
@ -42,7 +42,7 @@ module QA
end
def relate_issue(issue)
click_element(:related_issues_plus_button)
click_element('related-issues-plus-button')
fill_element(:add_issue_field, issue.web_url)
send_keys_to_element(:add_issue_field, :enter)
end
@ -74,14 +74,14 @@ module QA
def has_delete_issue_button?
open_actions_dropdown
has_element?(:delete_issue_button)
has_element?('delete-issue-button')
end
def delete_issue
has_delete_issue_button?
click_element(
:delete_issue_button,
'delete-issue-button',
Page::Modal::DeleteIssue,
wait: Support::Repeater::DEFAULT_MAX_WAIT_TIME
)

View File

@ -6,27 +6,27 @@ module QA
module Packages
class Index < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue' do
element :details_link
element 'details-link'
end
view 'app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue' do
element :details_link
element 'details-link'
end
def click_package(name)
click_element(:details_link, text: name)
click_element('details-link', text: name)
end
def has_package?(name)
has_element?(:details_link, text: name, wait: 20)
has_element?('details-link', text: name, wait: 20)
end
def has_module?(name)
has_element?(:details_link, text: name, wait: 20)
has_element?('details-link', text: name, wait: 20)
end
def has_no_package?(name)
has_no_element?(:details_link, text: name)
has_no_element?('details-link', text: name)
end
end
end

View File

@ -6,19 +6,19 @@ module QA
module Packages
class Show < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue' do
element :delete_package
element :delete_modal_button
element :package_information_content
element 'delete-package'
element 'delete-modal-button'
element 'package-information-content'
end
def has_package_info?(name, version)
has_element?(:package_information_content, text: /#{name}.*#{version}/)
has_element?('package-information-content', text: /#{name}.*#{version}/)
end
def click_delete
click_element(:delete_package)
wait_for_animated_element(:delete_modal_button)
click_element(:delete_modal_button)
click_element('delete-package')
wait_for_animated_element('delete-modal-button')
click_element('delete-modal-button')
end
end
end

View File

@ -6,21 +6,21 @@ module QA
module Registry
class Show < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue' do
element :details_link
element 'details-link'
end
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
element :additional_actions
element :single_delete_button
element 'additional-actions'
element 'single-delete-button'
element :name
end
def has_registry_repository?(name)
find_element(:details_link, text: name)
find_element('details-link', text: name)
end
def click_on_image(name)
click_element(:details_link, text: name)
click_element('details-link', text: name)
end
def has_tag?(tag_name)
@ -32,8 +32,8 @@ module QA
end
def click_delete
click_element(:additional_actions)
click_element(:single_delete_button)
click_element('additional-actions')
click_element('single-delete-button')
find_button('Delete').click
end
end

View File

@ -8,30 +8,30 @@ module QA
include QA::Page::Settings::Common
view 'app/assets/javascripts/security_configuration/components/app.vue' do
element :security_configuration_container
element :security_view_history_link
element 'security-configuration-container'
element 'security-view-history-link'
end
view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do
element :feature_status
element 'feature-status'
element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern
element :dependency_scanning_mr_button, "`${feature.type}_mr_button`" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue' do
element :autodevops_container
element 'autodevops-container'
end
def has_security_configuration_history_link?
has_element?(:security_view_history_link)
has_element?('security-view-history-link')
end
def has_no_security_configuration_history_link?
has_no_element?(:security_view_history_link)
has_no_element?('security-view-history-link')
end
def click_security_configuration_history_link
click_element(:security_view_history_link)
click_element('security-view-history-link')
end
def click_sast_enable_button
@ -43,31 +43,31 @@ module QA
end
def has_true_sast_status?
has_element?(:feature_status, feature: 'sast_true_status')
has_element?('feature-status', feature: 'sast_true_status')
end
def has_false_sast_status?
has_element?(:feature_status, feature: 'sast_false_status')
has_element?('feature-status', feature: 'sast_false_status')
end
def has_true_dependency_scanning_status?
has_element?(:feature_status, feature: 'dependency_scanning_true_status')
has_element?('feature-status', feature: 'dependency_scanning_true_status')
end
def has_false_dependency_scanning_status?
has_element?(:feature_status, feature: 'dependency_scanning_false_status')
has_element?('feature-status', feature: 'dependency_scanning_false_status')
end
def has_auto_devops_container?
has_element?(:autodevops_container)
has_element?('autodevops-container')
end
def has_no_auto_devops_container?
has_no_element?(:autodevops_container)
has_no_element?('autodevops-container')
end
def has_auto_devops_container_description?
within_element(:autodevops_container) do
within_element('autodevops-container') do
has_text?('Quickly enable all continuous testing and compliance tools by enabling Auto DevOps')
end
end
@ -79,7 +79,7 @@ module QA
private
def go_to_tab(name)
within_element(:security_configuration_container) do
within_element('security-configuration-container') do
find('.nav-item', text: name).click
end
end

View File

@ -19,8 +19,7 @@ module QA
click_ci_variable_save_button
wait_until(reload: false) do
# Using data-testid="ci-variable-table"
within_element(:ci_variable_table) { has_element?(:edit_ci_variable_button) }
within_element('ci-variable-table') { has_element?(:edit_ci_variable_button) }
end
end
@ -29,8 +28,7 @@ module QA
end
def click_edit_ci_variable
# Using data-testid="ci-variable-table"
within_element(:ci_variable_table) do
within_element('ci-variable-table') do
click_element :edit_ci_variable_button
end
end

View File

@ -6,65 +6,65 @@ module QA
module Settings
class DeployKeys < Page::Base
view 'app/views/shared/deploy_keys/_form.html.haml' do
element :deploy_key_title_field
element :deploy_key_field
element 'deploy-key-title-field'
element 'deploy-key-field'
end
view 'app/views/shared/deploy_keys/_project_group_form.html.haml' do
element :deploy_key_title_field
element :deploy_key_field
element :deploy_key_expires_at_field
element :add_deploy_key_button
element 'deploy-key-title-field'
element 'deploy-key-field'
element 'deploy-key-expires-at-field'
element 'add-deploy-key-button'
end
view 'app/assets/javascripts/deploy_keys/components/app.vue' do
element :project_deploy_keys_container
element 'project-deploy-keys-container'
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do
element :key_container
element :key_title_content
element :key_sha256_fingerprint_content
element 'key-container'
element 'key-title-content'
element 'key-sha256-fingerprint-content'
end
def add_key
click_element(:add_deploy_key_button)
click_element('add-deploy-key-button')
end
def fill_key_title(title)
fill_element(:deploy_key_title_field, title)
fill_element('deploy-key-title-field', title)
end
def fill_key_value(key)
fill_element(:deploy_key_field, key)
fill_element('deploy-key-field', key)
end
def find_sha256_fingerprint(title)
within_project_deploy_keys do
find_element(:key_container, text: title)
.find(element_selector_css(:key_sha256_fingerprint_content)).text
find_element('key-container', text: title)
.find(element_selector_css('key-sha256-fingerprint-content')).text
end
end
def has_key?(title, sha256_fingerprint)
within_project_deploy_keys do
find_element(:key_container, text: title)
.has_css?(element_selector_css(:key_sha256_fingerprint_content), text: sha256_fingerprint)
find_element('key-container', text: title)
.has_css?(element_selector_css('key-sha256-fingerprint-content'), text: sha256_fingerprint)
end
end
def key_title
within_project_deploy_keys do
find_element(:key_title_content).text
find_element('key-title-content').text
end
end
private
def within_project_deploy_keys
has_element?(:project_deploy_keys_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
has_element?('project-deploy-keys-container', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
within_element(:project_deploy_keys_container) do
within_element('project-deploy-keys-container') do
yield
end
end

View File

@ -20,7 +20,7 @@ module QA
end
view 'app/views/shared/deploy_keys/_index.html.haml' do
element :deploy_keys_settings_content
element 'deploy-keys-settings-content'
end
view 'app/views/projects/protected_tags/shared/_index.html.haml' do
@ -38,7 +38,7 @@ module QA
end
def expand_deploy_keys(&block)
expand_content(:deploy_keys_settings_content) do
expand_content('deploy-keys-settings-content') do
Settings::DeployKeys.perform(&block)
end
end

View File

@ -26,7 +26,7 @@ module QA
end
view 'app/views/layouts/header/_new_dropdown.html.haml' do
element :new_menu_toggle
element 'new-menu-toggle'
end
view 'app/views/projects/_last_push.html.haml' do
@ -50,7 +50,7 @@ module QA
end
view 'app/assets/javascripts/forks/components/forks_button.vue' do
element :fork_button
element 'fork-button'
end
view 'app/views/projects/empty.html.haml' do
@ -93,7 +93,7 @@ module QA
# Change back to regular click_element when vscode_web_ide FF is removed
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/371084
def fork_project
fork_button = find_element(:fork_button)
fork_button = find_element('fork-button')
click_by_javascript(fork_button)
end

View File

@ -43,7 +43,7 @@ module QA
#
# @return [void]
def within_new_item_menu
click_element(:new_menu_toggle)
click_element('new-menu-toggle')
yield
end

View File

@ -27,7 +27,7 @@ module QA
Flow::Login.sign_in_as_admin
Page::Main::Menu.perform do |menu|
menu.go_to_page_by_shortcut(:todos_shortcut_button)
menu.go_to_page_by_shortcut('todos-shortcut-button')
end
Page::Dashboard::Todos.perform do |todos|

View File

@ -185,6 +185,13 @@ RSpec.describe 'Container Registry', :js, feature_category: :groups_and_projects
visit_next_page
expect(page).to have_content 'my/image'
end
it 'pagination is preserved after navigating back from details' do
visit_next_page
click_link 'my/image'
page.go_back
expect(page).to have_content 'my/image'
end
end
def visit_container_registry

View File

@ -1,23 +1,18 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Component from '~/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue';
import ImageListRow from '~/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue';
import { imagesListResponse, pageInfo } from '../../mock_data';
import { imagesListResponse } from '../../mock_data';
describe('Image List', () => {
let wrapper;
const findRow = () => wrapper.findAllComponents(ImageListRow);
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
const { __typename, ...defaultPageInfo } = pageInfo;
const mountComponent = (props) => {
wrapper = shallowMount(Component, {
propsData: {
images: imagesListResponse,
pageInfo: defaultPageInfo,
...props,
},
});
@ -42,28 +37,4 @@ describe('Image List', () => {
expect(findRow().at(0).props('metadataLoading')).toBe(true);
});
});
describe('pagination', () => {
it('exists', () => {
mountComponent();
expect(findPagination().props()).toMatchObject({ ...defaultPageInfo });
});
it('emits "prev-page" when the user clicks the back page button', () => {
mountComponent();
findPagination().vm.$emit('prev');
expect(wrapper.emitted('prev-page')).toEqual([[]]);
});
it('emits "next-page" when the user clicks the forward page button', () => {
mountComponent();
findPagination().vm.$emit('next');
expect(wrapper.emitted('next-page')).toEqual([[]]);
});
});
});

View File

@ -23,6 +23,7 @@ import deleteContainerRepositoryMutation from '~/packages_and_registries/contain
import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
import component from '~/packages_and_registries/container_registry/explorer/pages/list.vue';
import Tracking from '~/tracking';
import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
@ -61,8 +62,10 @@ describe('List Page', () => {
const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]');
const findDeleteImage = () => wrapper.findComponent(DeleteImage);
const findPersistedPagination = () => wrapper.findComponent(PersistedPagination);
const fireFirstSortUpdate = () => {
findPersistedSearch().vm.$emit('update', { sort: 'UPDATED_DESC', filters: [] });
findPersistedSearch().vm.$emit('update', { sort: 'UPDATED_DESC', filters: [], pageInfo: {} });
};
const waitForApolloRequestRender = async () => {
@ -219,6 +222,12 @@ describe('List Page', () => {
expect(findImageList().exists()).toBe(false);
});
it('pagination is set to empty object', () => {
mountComponent();
expect(findPersistedPagination().props('pagination')).toEqual({});
});
it('cli commands is not visible', () => {
mountComponent();
@ -462,7 +471,15 @@ describe('List Page', () => {
});
describe('pagination', () => {
it('prev-page event triggers a fetchMore request', async () => {
it('exists', async () => {
mountComponent();
fireFirstSortUpdate();
await waitForApolloRequestRender();
expect(findPersistedPagination().props('pagination')).toEqual(pageInfo);
});
it('prev event triggers a previous page request', async () => {
const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
const detailsResolver = jest
.fn()
@ -471,7 +488,7 @@ describe('List Page', () => {
fireFirstSortUpdate();
await waitForApolloRequestRender();
findImageList().vm.$emit('prev-page');
findPersistedPagination().vm.$emit('prev');
await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
@ -490,7 +507,39 @@ describe('List Page', () => {
);
});
it('next-page event triggers a fetchMore request', async () => {
it('calls resolver with pagination params when persisted search returns before', async () => {
const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
const detailsResolver = jest
.fn()
.mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock);
mountComponent({ resolver, detailsResolver });
findPersistedSearch().vm.$emit('update', {
sort: 'UPDATED_DESC',
filters: [],
pageInfo: { before: pageInfo.startCursor },
});
await waitForApolloRequestRender();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({
sort: 'UPDATED_DESC',
before: pageInfo.startCursor,
first: null,
last: GRAPHQL_PAGE_SIZE,
}),
);
expect(detailsResolver).toHaveBeenCalledWith(
expect.objectContaining({
sort: 'UPDATED_DESC',
before: pageInfo.startCursor,
first: null,
last: GRAPHQL_PAGE_SIZE,
}),
);
});
it('next event triggers a next page request', async () => {
const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
const detailsResolver = jest
.fn()
@ -499,7 +548,7 @@ describe('List Page', () => {
fireFirstSortUpdate();
await waitForApolloRequestRender();
findImageList().vm.$emit('next-page');
findPersistedPagination().vm.$emit('next');
await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
@ -515,6 +564,36 @@ describe('List Page', () => {
}),
);
});
it('calls resolver with pagination params when persisted search returns after', async () => {
const resolver = jest.fn().mockResolvedValue(graphQLImageListMock);
const detailsResolver = jest
.fn()
.mockResolvedValue(graphQLProjectImageRepositoriesDetailsMock);
mountComponent({ resolver, detailsResolver });
findPersistedSearch().vm.$emit('update', {
sort: 'UPDATED_DESC',
filters: [],
pageInfo: { after: pageInfo.endCursor },
});
await waitForApolloRequestRender();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({
sort: 'UPDATED_DESC',
after: pageInfo.endCursor,
first: GRAPHQL_PAGE_SIZE,
}),
);
expect(detailsResolver).toHaveBeenCalledWith(
expect.objectContaining({
sort: 'UPDATED_DESC',
after: pageInfo.endCursor,
first: GRAPHQL_PAGE_SIZE,
}),
);
});
});
});

View File

@ -1,6 +1,9 @@
import {
getImageName,
timeTilRun,
getNextPageParams,
getPreviousPageParams,
getPageParams,
} from '~/packages_and_registries/container_registry/explorer/utils';
describe('Container registry utilities', () => {
@ -35,4 +38,49 @@ describe('Container registry utilities', () => {
expect(result).toBe('');
});
});
describe('getNextPageParams', () => {
it('should return the next page params with the provided cursor', () => {
const cursor = 'abc123';
expect(getNextPageParams(cursor)).toEqual({
after: cursor,
first: 10,
});
});
});
describe('getPreviousPageParams', () => {
it('should return the previous page params with the provided cursor', () => {
const cursor = 'abc123';
expect(getPreviousPageParams(cursor)).toEqual({
first: null,
before: cursor,
last: 10,
});
});
});
describe('getPageParams', () => {
it('should return the previous page params if before cursor is available', () => {
const pageInfo = { before: 'abc123' };
expect(getPageParams(pageInfo)).toEqual({
first: null,
before: pageInfo.before,
last: 10,
});
});
it('should return the next page params if after cursor is available', () => {
const pageInfo = { after: 'abc123' };
expect(getPageParams(pageInfo)).toEqual({
after: pageInfo.after,
first: 10,
});
});
it('should return an empty object if both before and after cursors are not available', () => {
const pageInfo = {};
expect(getPageParams(pageInfo)).toEqual({});
});
});
});

View File

@ -0,0 +1,100 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PersistedPagination from '~/packages_and_registries/shared/components/persisted_pagination.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
describe('Persisted Search', () => {
let wrapper;
const defaultProps = {
pagination: {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'eyJpZCI6IjI2In0',
endCursor: 'eyJpZCI6IjgifQ',
},
};
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
const findUrlSync = () => wrapper.findComponent(UrlSync);
const mountComponent = ({ propsData = defaultProps, stubs = {} } = {}) => {
wrapper = shallowMountExtended(PersistedPagination, {
propsData,
stubs: {
UrlSync,
...stubs,
},
});
};
it('has pagination component', () => {
mountComponent();
const { hasNextPage, hasPreviousPage, startCursor, endCursor } = defaultProps.pagination;
expect(findPagination().props('hasNextPage')).toBe(hasNextPage);
expect(findPagination().props('hasPreviousPage')).toBe(hasPreviousPage);
expect(findPagination().props('startCursor')).toBe(startCursor);
expect(findPagination().props('endCursor')).toBe(endCursor);
});
it('has a UrlSync component', () => {
mountComponent();
expect(findUrlSync().exists()).toBe(true);
});
describe('pagination events', () => {
const updateQueryMock = jest.fn();
const mockUrlSync = {
methods: {
updateQuery: updateQueryMock,
},
render() {
return this.$scopedSlots.default?.({ updateQuery: this.updateQuery });
},
};
beforeEach(() => {
mountComponent({ stubs: { UrlSync: mockUrlSync } });
});
afterEach(() => {
updateQueryMock.mockReset();
});
describe('prev event', () => {
beforeEach(() => {
findPagination().vm.$emit('prev');
});
it('calls updateQuery mock with right params', () => {
expect(updateQueryMock).toHaveBeenCalledWith({
before: defaultProps.pagination?.startCursor,
after: null,
});
});
it('re-emits prev event', () => {
expect(wrapper.emitted('prev')).toHaveLength(1);
});
});
describe('next event', () => {
beforeEach(() => {
findPagination().vm.$emit('next');
});
it('calls updateQuery mock with right params', () => {
expect(updateQueryMock).toHaveBeenCalledWith({
after: defaultProps.pagination.endCursor,
before: null,
});
});
it('re-emits next event', () => {
expect(wrapper.emitted('next')).toHaveLength(1);
});
});
});
});

View File

@ -1,16 +1,21 @@
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import component from '~/packages_and_registries/shared/components/persisted_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
import {
getQueryParams,
extractFilterAndSorting,
extractPageInfo,
} from '~/packages_and_registries/shared/utils';
jest.mock('~/packages_and_registries/shared/utils');
useMockLocationHelper();
Vue.use(VueRouter);
describe('Persisted Search', () => {
let router;
let wrapper;
const defaultQueryParamsMock = {
@ -31,8 +36,11 @@ describe('Persisted Search', () => {
const findUrlSync = () => wrapper.findComponent(UrlSync);
const mountComponent = (propsData = defaultProps) => {
router = new VueRouter({ mode: 'history' });
wrapper = shallowMountExtended(component, {
propsData,
router,
stubs: {
UrlSync,
},
@ -41,6 +49,10 @@ describe('Persisted Search', () => {
beforeEach(() => {
extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock);
extractPageInfo.mockReturnValue({
after: '123',
before: null,
});
});
it('has a registry search component', async () => {
@ -63,6 +75,48 @@ describe('Persisted Search', () => {
expect(findUrlSync().exists()).toBe(true);
});
it('emits update event on mount', () => {
mountComponent();
expect(wrapper.emitted('update')[0]).toEqual([
{
filters: ['foo'],
sort: 'TEST_DESC',
pageInfo: {
after: '123',
before: null,
},
},
]);
});
it('re-emits update event when route changes', async () => {
mountComponent();
extractFilterAndSorting.mockReturnValue({
filters: [],
sorting: {},
});
extractPageInfo.mockReturnValue({
after: null,
before: '456',
});
await router.push({ query: { before: '456' } });
// there is always a first call on mounted that emits up default values
expect(wrapper.emitted('update')[1]).toEqual([
{
filters: [],
sort: 'TEST_DESC',
pageInfo: {
before: '456',
after: null,
},
},
]);
});
it('on sorting:changed emits update event and update internal sort', async () => {
const payload = { sort: 'desc', orderBy: 'test' };
@ -81,6 +135,7 @@ describe('Persisted Search', () => {
{
filters: ['foo'],
sort: 'TEST_DESC',
pageInfo: {},
},
]);
});
@ -110,6 +165,10 @@ describe('Persisted Search', () => {
{
filters: ['foo'],
sort: 'TEST_DESC',
pageInfo: {
after: '123',
before: null,
},
},
]);
});
@ -126,7 +185,7 @@ describe('Persisted Search', () => {
expect(UrlSync.methods.updateQuery).toHaveBeenCalled();
});
it('sets the component sorting and filtering based on the querystring', async () => {
it('sets the component sorting, filtering and page info based on the querystring', async () => {
mountComponent();
await nextTick();

View File

@ -3,6 +3,7 @@ import {
keyValueToFilterToken,
searchArrayToFilterTokens,
extractFilterAndSorting,
extractPageInfo,
beautifyPath,
getCommitLink,
} from '~/packages_and_registries/shared/utils';
@ -61,6 +62,21 @@ describe('Packages And Registries shared utils', () => {
);
});
describe('extractPageInfo', () => {
it.each`
after | before | result
${null} | ${null} | ${{ after: null, before: null }}
${'123'} | ${null} | ${{ after: '123', before: null }}
${null} | ${'123'} | ${{ after: null, before: '123' }}
`('returns pagination objects', ({ after, before, result }) => {
const queryObject = {
after,
before,
};
expect(extractPageInfo(queryObject)).toStrictEqual(result);
});
});
describe('beautifyPath', () => {
it('returns a string with spaces around /', () => {
expect(beautifyPath('foo/bar')).toBe('foo / bar');

View File

@ -25,6 +25,8 @@ describe('Registry Search', () => {
orderBy: 'name',
search: [],
sort: 'asc',
after: null,
before: null,
};
const mountComponent = (propsData = defaultProps) => {

View File

@ -18,6 +18,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA
@ -43,6 +44,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
CI_PIPELINE_IID
CI_PIPELINE_SOURCE
CI_PIPELINE_CREATED_AT
CI_PIPELINE_NAME
CI_COMMIT_SHA
CI_COMMIT_SHORT_SHA
CI_COMMIT_BEFORE_SHA

View File

@ -111,6 +111,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, featur
value: pipeline.source },
{ key: 'CI_PIPELINE_CREATED_AT',
value: pipeline.created_at.iso8601 },
{ key: 'CI_PIPELINE_NAME',
value: pipeline.name },
{ key: 'CI_COMMIT_SHA',
value: job.sha },
{ key: 'CI_COMMIT_SHORT_SHA',

View File

@ -2400,6 +2400,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
{ key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false },
{ key: 'CI_PIPELINE_NAME', value: pipeline.name, public: true, masked: false },
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false },
{ key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false },
{ key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false },

View File

@ -71,7 +71,7 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
it 'toggles services desk incoming email' do
project.update!(service_desk_enabled: false)
put project_service_desk_refresh_path(project, format: :json), params: { service_desk_enabled: true }
put project_service_desk_path(project, format: :json), params: { service_desk_enabled: true }
expect(json_response["service_desk_address"]).to be_present
expect(json_response["service_desk_enabled"]).to be_truthy
@ -79,7 +79,7 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
end
it 'sets issue_template_key' do
put project_service_desk_refresh_path(project, format: :json), params: { issue_template_key: 'service_desk' }
put project_service_desk_path(project, format: :json), params: { issue_template_key: 'service_desk' }
settings = project.service_desk_setting
expect(settings).to be_present
@ -89,7 +89,7 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
end
it 'returns an error when update of service desk settings fails' do
put project_service_desk_refresh_path(project, format: :json), params: { issue_template_key: 'invalid key' }
put project_service_desk_path(project, format: :json), params: { issue_template_key: 'invalid key' }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('Issue template key is empty or does not exist')
@ -100,7 +100,7 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
it 'renders 404' do
sign_in(other_user)
put project_service_desk_refresh_path(project, format: :json), params: { service_desk_enabled: true }
put project_service_desk_path(project, format: :json), params: { service_desk_enabled: true }
expect(response).to have_gitlab_http_status(:not_found)
end