diff --git a/app/assets/javascripts/tags/components/sort_dropdown.vue b/app/assets/javascripts/tags/components/sort_dropdown.vue
index 90a4788074d1385ae581fb33f21fbd832951f8b7..32bc901456b5db1df9533dd59baf3df2724838ed 100644
--- a/app/assets/javascripts/tags/components/sort_dropdown.vue
+++ b/app/assets/javascripts/tags/components/sort_dropdown.vue
@@ -1,57 +1,69 @@
@@ -59,16 +71,17 @@ export default {
-
diff --git a/app/assets/javascripts/tags/constants.js b/app/assets/javascripts/tags/constants.js
index a8096a08a97b58ce4f2a9bc3d4531335f09a0544..246831aa2b130bee954d99d3bc4f53c317f110b6 100644
--- a/app/assets/javascripts/tags/constants.js
+++ b/app/assets/javascripts/tags/constants.js
@@ -35,3 +35,9 @@ export const I18N_DELETE_TAG_MODAL = {
deleteButtonText: DELETE_BUTTON_TEXT,
deleteButtonTextProtectedTag: DELETE_BUTTON_TEXT_PROTECTED_TAG,
};
+
+export const SORT_DIRECTION_ASC = 'asc';
+export const SORT_DIRECTION_DESC = 'desc';
+export const SORT_OPTION_NAME = 'name';
+export const SORT_OPTION_UPDATED = 'updated';
+export const SORT_OPTION_VERSION = 'version';
diff --git a/app/assets/javascripts/tags/index.js b/app/assets/javascripts/tags/index.js
index 078b6d0a84722086922f27d0fb494f88c2337a68..e072194c1c3fe915d834b984300f8c776d4c24d3 100644
--- a/app/assets/javascripts/tags/index.js
+++ b/app/assets/javascripts/tags/index.js
@@ -3,7 +3,7 @@ import initSourceCodeDropdowns from '~/vue_shared/components/download_dropdown/i
import SortDropdown from './components/sort_dropdown.vue';
const mountDropdownApp = (el) => {
- const { sortOptions, filterTagsPath } = el.dataset;
+ const { filterTagsPath } = el.dataset;
return new Vue({
el,
@@ -12,7 +12,6 @@ const mountDropdownApp = (el) => {
SortDropdown,
},
provide: {
- sortOptions: JSON.parse(sortOptions),
filterTagsPath,
},
render: (createElement) => createElement(SortDropdown),
diff --git a/spec/frontend/tags/components/sort_dropdown_spec.js b/spec/frontend/tags/components/sort_dropdown_spec.js
index a0ba263e83280d5fecdfca35c1465078edacca96..4ceead1123ff74c780f94e55a2383f067c577fe8 100644
--- a/spec/frontend/tags/components/sort_dropdown_spec.js
+++ b/spec/frontend/tags/components/sort_dropdown_spec.js
@@ -1,89 +1,124 @@
-import { GlListboxItem, GlSearchBoxByClick } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import * as urlUtils from '~/lib/utils/url_utility';
+import { GlSearchBoxByClick, GlSorting } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { SORT_OPTION_NAME, SORT_OPTION_UPDATED, SORT_OPTION_VERSION } from '~/tags/constants';
+
+import { visitUrl } from '~/lib/utils/url_utility';
import SortDropdown from '~/tags/components/sort_dropdown.vue';
import setWindowLocation from 'helpers/set_window_location_helper';
-describe('Tags sort dropdown', () => {
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
+
+describe('SortDropdown', () => {
let wrapper;
- const createWrapper = (props = {}) => {
- return extendedWrapper(
- mount(SortDropdown, {
- provide: {
- filterTagsPath: '/root/ci-cd-project-demo/-/tags',
- sortOptions: {
- name_asc: 'Name',
- updated_asc: 'Oldest updated',
- updated_desc: 'Last updated',
- },
- ...props,
- },
- }),
- );
+ const defaultPath = '/root/ci-cd-project-demo/-/tags';
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(SortDropdown, {
+ provide: { filterTagsPath: defaultPath },
+ ...props,
+ });
};
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByClick);
- const findTagsDropdown = () => wrapper.findByTestId('tags-dropdown');
+ const findSorting = () => wrapper.findComponent(GlSorting);
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
- describe('default state', () => {
+ describe('default rendering', () => {
beforeEach(() => {
- wrapper = createWrapper();
+ setWindowLocation(defaultPath);
+ createComponent();
});
- it('should have a search box with a placeholder', () => {
- const searchBox = findSearchBox();
+ it('renders a search box with correct placeholder', () => {
+ expect(findSearchBox().props('placeholder')).toBe('Filter by tag name');
+ });
- expect(searchBox.exists()).toBe(true);
- expect(searchBox.find('input').attributes('placeholder')).toBe('Filter by tag name');
+ it('renders a sorting component with SORT_OPTIONS', () => {
+ const SORT_OPTIONS = [
+ { value: SORT_OPTION_NAME, text: 'Name' },
+ { value: SORT_OPTION_UPDATED, text: 'Updated date' },
+ { value: SORT_OPTION_VERSION, text: 'Version' },
+ ];
+ expect(findSorting().props('sortOptions')).toEqual(SORT_OPTIONS);
});
- it('should have a sort order dropdown', () => {
- const tagsDropdown = findTagsDropdown();
+ it('has default sortBy=updated and order=desc', () => {
+ expect(findSorting().props('sortBy')).toBe('updated');
+ expect(findSorting().props('isAscending')).toBe(false);
+ });
+ });
+
+ describe('when URL contains query parameters', () => {
+ beforeEach(() => {
+ setWindowLocation(`${defaultPath}?search=release&sort=updated_desc`);
+ createComponent();
+ });
- expect(tagsDropdown.exists()).toBe(true);
+ it('initializes state from URL params', () => {
+ expect(findSearchBox().props('value')).toBe('release');
+ expect(findSorting().props('sortBy')).toBe('updated');
+ expect(findSorting().props('isAscending')).toBe(false);
});
});
- describe('when url contains a search param', () => {
- const branchName = 'branch-1';
+ describe('on search submit', () => {
+ beforeEach(() => {
+ setWindowLocation(defaultPath);
+ createComponent();
+ });
+
+ it('navigates with search, sort, and order params', async () => {
+ await findSearchBox().vm.$emit('input', 'frontend');
+ await findSearchBox().vm.$emit('submit');
+ expect(visitUrl).toHaveBeenCalledWith(`${defaultPath}?sort=updated_desc&search=frontend`);
+ });
+ });
+
+ describe('on sort changes', () => {
beforeEach(() => {
- setWindowLocation(`/root/ci-cd-project-demo/-/branches?search=${branchName}`);
- wrapper = createWrapper();
+ setWindowLocation(defaultPath);
+ createComponent();
});
- it('should set the default the input value to search param', () => {
- expect(findSearchBox().props('value')).toBe(branchName);
+ it('calls visitUrl when sortBy changes', async () => {
+ await findSorting().vm.$emit('sortByChange', 'version');
+
+ expect(visitUrl).toHaveBeenCalledWith(`${defaultPath}?sort=version_desc`);
});
});
- describe('when submitting a search term', () => {
+ describe('when sortDirection changes', () => {
beforeEach(() => {
- urlUtils.visitUrl = jest.fn();
- wrapper = createWrapper();
+ setWindowLocation(defaultPath);
+ createComponent();
});
- it('should call visitUrl', () => {
- const searchTerm = 'branch-1';
- const searchBox = findSearchBox();
- searchBox.vm.$emit('input', searchTerm);
- searchBox.vm.$emit('submit');
+ it('calls visitUrl when sort direction changes', async () => {
+ await findSorting().vm.$emit('sortDirectionChange', true); // ascending
- expect(urlUtils.visitUrl).toHaveBeenCalledWith(
- '/root/ci-cd-project-demo/-/tags?search=branch-1&sort=updated_desc',
- );
+ expect(visitUrl).toHaveBeenCalledWith(`${defaultPath}?sort=updated_asc`);
});
+ });
- it('should send a sort parameter', () => {
- const sortDropdownItem = findTagsDropdown().findAllComponents(GlListboxItem).at(0);
+ describe('when search term is empty', () => {
+ beforeEach(() => {
+ setWindowLocation(defaultPath);
+ createComponent();
+ });
- sortDropdownItem.trigger('click');
+ it('omits search parameter in URL', async () => {
+ await findSearchBox().vm.$emit('input', '');
+ wrapper.vm.visitUrlFromOption();
- expect(urlUtils.visitUrl).toHaveBeenCalledWith(
- '/root/ci-cd-project-demo/-/tags?sort=name_asc',
- );
+ expect(visitUrl).toHaveBeenCalledWith(`${defaultPath}?sort=updated_desc`);
});
});
});