Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/ref')
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js532
-rw-r--r--spec/frontend/ref/stores/actions_spec.js180
-rw-r--r--spec/frontend/ref/stores/getters_spec.js36
-rw-r--r--spec/frontend/ref/stores/mutations_spec.js274
4 files changed, 1022 insertions, 0 deletions
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
new file mode 100644
index 00000000000..2688e4b3428
--- /dev/null
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -0,0 +1,532 @@
+import Vuex from 'vuex';
+import { mount, createLocalVue } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { GlLoadingIcon, GlSearchBoxByType, GlNewDropdownItem, GlIcon } from '@gitlab/ui';
+import { trimText } from 'helpers/text_helper';
+import { sprintf } from '~/locale';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants';
+import createStore from '~/ref/stores/';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Ref selector component', () => {
+ const fixtures = {
+ branches: getJSONFixture('api/branches/branches.json'),
+ tags: getJSONFixture('api/tags/tags.json'),
+ commit: getJSONFixture('api/commits/commit.json'),
+ };
+
+ const projectId = '8';
+
+ let wrapper;
+ let branchesApiCallSpy;
+ let tagsApiCallSpy;
+ let commitApiCallSpy;
+
+ const createComponent = () => {
+ wrapper = mount(RefSelector, {
+ propsData: {
+ projectId,
+ value: '',
+ },
+ listeners: {
+ // simulate a parent component v-model binding
+ input: selectedRef => {
+ wrapper.setProps({ value: selectedRef });
+ },
+ },
+ stubs: {
+ GlSearchBoxByType: true,
+ },
+ localVue,
+ store: createStore(),
+ });
+ };
+
+ beforeEach(() => {
+ const mock = new MockAdapter(axios);
+ gon.api_version = 'v4';
+
+ branchesApiCallSpy = jest
+ .fn()
+ .mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]);
+
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/branches`)
+ .reply(config => branchesApiCallSpy(config));
+ mock
+ .onGet(`/api/v4/projects/${projectId}/repository/tags`)
+ .reply(config => tagsApiCallSpy(config));
+ mock
+ .onGet(new RegExp(`/api/v4/projects/${projectId}/repository/commits/.*`))
+ .reply(config => commitApiCallSpy(config));
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ //
+ // Finders
+ //
+ const findButtonContent = () => wrapper.find('[data-testid="button-content"]');
+
+ const findNoResults = () => wrapper.find('[data-testid="no-results"]');
+
+ const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+
+ const findBranchesSection = () => wrapper.find('[data-testid="branches-section"]');
+ const findBranchDropdownItems = () => findBranchesSection().findAll(GlNewDropdownItem);
+ const findFirstBranchDropdownItem = () => findBranchDropdownItems().at(0);
+
+ const findTagsSection = () => wrapper.find('[data-testid="tags-section"]');
+ const findTagDropdownItems = () => findTagsSection().findAll(GlNewDropdownItem);
+ const findFirstTagDropdownItem = () => findTagDropdownItems().at(0);
+
+ const findCommitsSection = () => wrapper.find('[data-testid="commits-section"]');
+ const findCommitDropdownItems = () => findCommitsSection().findAll(GlNewDropdownItem);
+ const findFirstCommitDropdownItem = () => findCommitDropdownItems().at(0);
+
+ //
+ // Expecters
+ //
+ const branchesSectionContainsErrorMessage = () => {
+ const branchesSection = findBranchesSection();
+
+ return branchesSection.text().includes(DEFAULT_I18N.branchesErrorMessage);
+ };
+
+ const tagsSectionContainsErrorMessage = () => {
+ const tagsSection = findTagsSection();
+
+ return tagsSection.text().includes(DEFAULT_I18N.tagsErrorMessage);
+ };
+
+ const commitsSectionContainsErrorMessage = () => {
+ const commitsSection = findCommitsSection();
+
+ return commitsSection.text().includes(DEFAULT_I18N.commitsErrorMessage);
+ };
+
+ //
+ // Convenience methods
+ //
+ const updateQuery = newQuery => {
+ wrapper.find(GlSearchBoxByType).vm.$emit('input', newQuery);
+ };
+
+ const selectFirstBranch = () => {
+ findFirstBranchDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstTag = () => {
+ findFirstTagDropdownItem().vm.$emit('click');
+ };
+
+ const selectFirstCommit = () => {
+ findFirstCommitDropdownItem().vm.$emit('click');
+ };
+
+ const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
+ axios.waitForAll().then(() => {
+ if (andClearMocks) {
+ branchesApiCallSpy.mockClear();
+ tagsApiCallSpy.mockClear();
+ commitApiCallSpy.mockClear();
+ }
+ });
+
+ describe('initialization behavior', () => {
+ beforeEach(createComponent);
+
+ it('initializes the dropdown with branches and tags when mounted', () => {
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('shows a spinner while network requests are in progress', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+
+ return waitForRequests().then(() => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('post-initialization behavior', () => {
+ describe('when the search query is updated', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests({ andClearMocks: true });
+ });
+
+ it('requeries the endpoints when the search query is updated', () => {
+ updateQuery('v1.2.3');
+
+ return waitForRequests().then(() => {
+ expect(branchesApiCallSpy).toHaveBeenCalledTimes(1);
+ expect(tagsApiCallSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ it("does not make a call to the commit endpoint if the query doesn't look like a SHA", () => {
+ updateQuery('not a sha');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ it('searches for a commit if the query could potentially be a SHA', () => {
+ updateQuery('abcdef');
+
+ return waitForRequests().then(() => {
+ expect(commitApiCallSpy).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when no results are found', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ describe('when the search query is empty', () => {
+ it('renders a "no results" message', () => {
+ expect(findNoResults().text()).toBe(DEFAULT_I18N.noResults);
+ });
+ });
+
+ describe('when the search query is not empty', () => {
+ const query = 'hello';
+
+ beforeEach(() => {
+ updateQuery(query);
+
+ return waitForRequests();
+ });
+
+ it('renders a "no results" message that includes the search query', () => {
+ expect(findNoResults().text()).toBe(sprintf(DEFAULT_I18N.noResultsWithQuery, { query }));
+ });
+ });
+ });
+
+ describe('branches', () => {
+ describe('when the branches search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it('renders the "Branches" heading with a total number indicator', () => {
+ expect(
+ findBranchesSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Branches 123');
+ });
+
+ it("does not render an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each non-default branch as a selectable item', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ fixtures.branches.forEach((b, i) => {
+ if (!b.default) {
+ expect(dropdownItems.at(i).text()).toBe(b.name);
+ }
+ });
+ });
+
+ it('renders the default branch as a selectable item with a "default" badge', () => {
+ const dropdownItems = findBranchDropdownItems();
+
+ const defaultBranch = fixtures.branches.find(b => b.default);
+ const defaultBranchIndex = fixtures.branches.indexOf(defaultBranch);
+
+ expect(trimText(dropdownItems.at(defaultBranchIndex).text())).toBe(
+ `${defaultBranch.name} default`,
+ );
+ });
+ });
+
+ describe('when the branches search returns no results', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the branches search returns an error', () => {
+ beforeEach(() => {
+ branchesApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the branches section in the dropdown', () => {
+ expect(findBranchesSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the branches section's body", () => {
+ expect(branchesSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('tags', () => {
+ describe('when the tags search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Tags" heading with a total number indicator', () => {
+ expect(
+ findTagsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Tags 456');
+ });
+
+ it("does not render an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each tag as a selectable item', () => {
+ const dropdownItems = findTagDropdownItems();
+
+ fixtures.tags.forEach((t, i) => {
+ expect(dropdownItems.at(i).text()).toBe(t.name);
+ });
+ });
+ });
+
+ describe('when the tags search returns no results', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([200, [], { [X_TOTAL_HEADER]: '0' }]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('does not render the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the tags search returns an error', () => {
+ beforeEach(() => {
+ tagsApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ return waitForRequests();
+ });
+
+ it('renders the tags section in the dropdown', () => {
+ expect(findTagsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the tags section's body", () => {
+ expect(tagsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('commits', () => {
+ describe('when the commit search returns results', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commit section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it('renders the "Commits" heading with a total number indicator', () => {
+ expect(
+ findCommitsSection()
+ .find('[data-testid="section-header"]')
+ .text(),
+ ).toBe('Commits 1');
+ });
+
+ it("does not render an error message in the comits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(false);
+ });
+
+ it('renders each commit as a selectable item with the short SHA and commit title', () => {
+ const dropdownItems = findCommitDropdownItems();
+
+ const { commit } = fixtures;
+
+ expect(dropdownItems.at(0).text()).toBe(`${commit.short_id} ${commit.title}`);
+ });
+ });
+
+ describe('when the commit search returns no results (i.e. a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([404]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('does not render the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(false);
+ });
+ });
+
+ describe('when the commit search returns an error (other than a 404)', () => {
+ beforeEach(() => {
+ commitApiCallSpy = jest.fn().mockReturnValue([500]);
+
+ createComponent();
+
+ updateQuery('abcd1234');
+
+ return waitForRequests();
+ });
+
+ it('renders the commits section in the dropdown', () => {
+ expect(findCommitsSection().exists()).toBe(true);
+ });
+
+ it("renders an error message in the commits section's body", () => {
+ expect(commitsSectionContainsErrorMessage()).toBe(true);
+ });
+ });
+ });
+
+ describe('selection', () => {
+ beforeEach(() => {
+ createComponent();
+
+ updateQuery(fixtures.commit.short_id);
+
+ return waitForRequests();
+ });
+
+ it('renders a checkmark by the selected item', () => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).toHaveClass(
+ 'gl-visibility-hidden',
+ );
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findFirstBranchDropdownItem().find(GlIcon).element).not.toHaveClass(
+ 'gl-visibility-hidden',
+ );
+ });
+ });
+
+ describe('when a branch is seleceted', () => {
+ it("displays the branch name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstBranch();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the branch's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstBranch();
+
+ expect(wrapper.vm.value).toEqual(fixtures.branches[0].name);
+ });
+ });
+
+ describe('when a tag is seleceted', () => {
+ it("displays the tag name in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstTag();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
+ });
+ });
+
+ it("updates the v-model binding with the tag's name", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstTag();
+
+ expect(wrapper.vm.value).toEqual(fixtures.tags[0].name);
+ });
+ });
+
+ describe('when a commit is selected', () => {
+ it("displays the full SHA in the dropdown's button", () => {
+ expect(findButtonContent().text()).toBe(DEFAULT_I18N.noRefSelected);
+
+ selectFirstCommit();
+
+ return localVue.nextTick().then(() => {
+ expect(findButtonContent().text()).toBe(fixtures.commit.id);
+ });
+ });
+
+ it("updates the v-model binding with the commit's full SHA", () => {
+ expect(wrapper.vm.value).toEqual('');
+
+ selectFirstCommit();
+
+ expect(wrapper.vm.value).toEqual(fixtures.commit.id);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/actions_spec.js b/spec/frontend/ref/stores/actions_spec.js
new file mode 100644
index 00000000000..32966354c95
--- /dev/null
+++ b/spec/frontend/ref/stores/actions_spec.js
@@ -0,0 +1,180 @@
+import testAction from 'helpers/vuex_action_helper';
+import createState from '~/ref/stores/state';
+import * as actions from '~/ref/stores/actions';
+import * as types from '~/ref/stores/mutation_types';
+
+let mockBranchesReturnValue;
+let mockTagsReturnValue;
+let mockCommitReturnValue;
+
+jest.mock('~/api', () => ({
+ // `__esModule: true` is required when mocking modules with default exports:
+ // https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options
+ __esModule: true,
+ default: {
+ branches: () => mockBranchesReturnValue,
+ tags: () => mockTagsReturnValue,
+ commit: () => mockCommitReturnValue,
+ },
+}));
+
+describe('Ref selector Vuex store actions', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('setProjectId', () => {
+ it(`commits ${types.SET_PROJECT_ID} with the new project ID`, () => {
+ const projectId = '4';
+ testAction(actions.setProjectId, projectId, state, [
+ { type: types.SET_PROJECT_ID, payload: projectId },
+ ]);
+ });
+ });
+
+ describe('setSelectedRef', () => {
+ it(`commits ${types.SET_SELECTED_REF} with the new selected ref name`, () => {
+ const selectedRef = 'v1.2.3';
+ testAction(actions.setSelectedRef, selectedRef, state, [
+ { type: types.SET_SELECTED_REF, payload: selectedRef },
+ ]);
+ });
+ });
+
+ describe('search', () => {
+ it(`commits ${types.SET_QUERY} with the new search query`, () => {
+ const query = 'hello';
+ testAction(
+ actions.search,
+ query,
+ state,
+ [{ type: types.SET_QUERY, payload: query }],
+ [{ type: 'searchBranches' }, { type: 'searchTags' }, { type: 'searchCommits' }],
+ );
+ });
+ });
+
+ describe('searchBranches', () => {
+ describe('when the search is successful', () => {
+ const branchesApiResponse = { data: [{ name: 'my-feature-branch' }] };
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.resolve(branchesApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_SUCCESS, payload: branchesApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockBranchesReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_BRANCHES_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchBranches, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_BRANCHES_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchTags', () => {
+ describe('when the search is successful', () => {
+ const tagsApiResponse = { data: [{ name: 'v1.2.3' }] };
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.resolve(tagsApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_SUCCESS, payload: tagsApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockTagsReturnValue = Promise.reject(error);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_TAGS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchTags, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_TAGS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+
+ describe('searchCommits', () => {
+ describe('when the search query potentially matches a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = true;
+ });
+
+ describe('when the search is successful', () => {
+ const commitApiResponse = { data: [{ id: 'abcd1234' }] };
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.resolve(commitApiResponse);
+ });
+
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_SUCCESS} with the response from the API, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_SUCCESS, payload: commitApiResponse },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+
+ describe('when the search fails', () => {
+ const error = new Error('Something went wrong!');
+
+ beforeEach(() => {
+ mockCommitReturnValue = Promise.reject(error);
+ });
+
+ describe('when the search query might match a commit SHA', () => {
+ it(`commits ${types.REQUEST_START}, ${types.RECEIVE_COMMITS_ERROR} with the error object, and ${types.REQUEST_FINISH}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.REQUEST_START },
+ { type: types.RECEIVE_COMMITS_ERROR, payload: error },
+ { type: types.REQUEST_FINISH },
+ ]);
+ });
+ });
+ });
+ });
+
+ describe('when the search query will not match a commit SHA', () => {
+ beforeEach(() => {
+ state.isQueryPossiblyASha = false;
+ });
+
+ it(`commits ${types.RESET_COMMIT_MATCHES}`, () => {
+ return testAction(actions.searchCommits, undefined, state, [
+ { type: types.RESET_COMMIT_MATCHES },
+ ]);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/getters_spec.js b/spec/frontend/ref/stores/getters_spec.js
new file mode 100644
index 00000000000..49d74e5b9e4
--- /dev/null
+++ b/spec/frontend/ref/stores/getters_spec.js
@@ -0,0 +1,36 @@
+import * as getters from '~/ref/stores/getters';
+
+describe('Ref selector Vuex store getters', () => {
+ describe('isQueryPossiblyASha', () => {
+ it.each`
+ query | isPossiblyASha
+ ${'abcd'} | ${true}
+ ${'ABCD'} | ${true}
+ ${'0123456789abcdef0123456789abcdef01234567'} | ${true}
+ ${'0123456789abcdef0123456789abcdef012345678'} | ${false}
+ ${'abc'} | ${false}
+ ${'ghij'} | ${false}
+ ${' abcd'} | ${false}
+ ${''} | ${false}
+ ${null} | ${false}
+ ${undefined} | ${false}
+ `(
+ 'returns true when the query potentially refers to a commit SHA',
+ ({ query, isPossiblyASha }) => {
+ expect(getters.isQueryPossiblyASha({ query })).toBe(isPossiblyASha);
+ },
+ );
+ });
+
+ describe('isLoading', () => {
+ it.each`
+ requestCount | isLoading
+ ${2} | ${true}
+ ${1} | ${true}
+ ${0} | ${false}
+ ${-1} | ${false}
+ `('returns true when at least one request is in progress', ({ requestCount, isLoading }) => {
+ expect(getters.isLoading({ requestCount })).toBe(isLoading);
+ });
+ });
+});
diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js
new file mode 100644
index 00000000000..78117436c33
--- /dev/null
+++ b/spec/frontend/ref/stores/mutations_spec.js
@@ -0,0 +1,274 @@
+import createState from '~/ref/stores/state';
+import mutations from '~/ref/stores/mutations';
+import * as types from '~/ref/stores/mutation_types';
+import { X_TOTAL_HEADER } from '~/ref/constants';
+
+describe('Ref selector Vuex store mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState();
+ });
+
+ describe('initial state', () => {
+ it('is created with the correct structure and initial values', () => {
+ expect(state).toEqual({
+ projectId: null,
+
+ query: '',
+ matches: {
+ branches: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ tags: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ commits: {
+ list: [],
+ totalCount: 0,
+ error: null,
+ },
+ },
+ selectedRef: null,
+ requestCount: 0,
+ });
+ });
+ });
+
+ describe(`${types.SET_PROJECT_ID}`, () => {
+ it('updates the project ID', () => {
+ const newProjectId = '4';
+ mutations[types.SET_PROJECT_ID](state, newProjectId);
+
+ expect(state.projectId).toBe(newProjectId);
+ });
+ });
+
+ describe(`${types.SET_SELECTED_REF}`, () => {
+ it('updates the selected ref', () => {
+ const newSelectedRef = 'my-feature-branch';
+ mutations[types.SET_SELECTED_REF](state, newSelectedRef);
+
+ expect(state.selectedRef).toBe(newSelectedRef);
+ });
+ });
+
+ describe(`${types.SET_QUERY}`, () => {
+ it('updates the search query', () => {
+ const newQuery = 'hello';
+ mutations[types.SET_QUERY](state, newQuery);
+
+ expect(state.query).toBe(newQuery);
+ });
+ });
+
+ describe(`${types.REQUEST_START}`, () => {
+ it('increments requestCount by 1', () => {
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_START](state);
+ expect(state.requestCount).toBe(3);
+ });
+ });
+
+ describe(`${types.REQUEST_FINISH}`, () => {
+ it('decrements requestCount by 1', () => {
+ state.requestCount = 3;
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(2);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(1);
+
+ mutations[types.REQUEST_FINISH](state);
+ expect(state.requestCount).toBe(0);
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_SUCCESS}`, () => {
+ it('updates state.matches.branches based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'master',
+ default: true,
+
+ // everything except "name" and "default" should be stripped
+ merged: false,
+ protected: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 37,
+ },
+ };
+
+ mutations[types.RECEIVE_BRANCHES_SUCCESS](state, response);
+
+ expect(state.matches.branches).toEqual({
+ list: [
+ {
+ name: 'master',
+ default: true,
+ },
+ {
+ name: 'my-feature-branch',
+ default: false,
+ },
+ ],
+ totalCount: 37,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_BRANCHES_ERROR}`, () => {
+ it('updates state.matches.branches to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.branches = {
+ list: [{ name: 'my-feature-branch' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_BRANCHES_ERROR](state, error);
+
+ expect(state.matches.branches).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_REQUEST_TAGS_SUCCESS}`, () => {
+ it('updates state.matches.tags based on the provided API response', () => {
+ const response = {
+ data: [
+ {
+ name: 'v1.2',
+
+ // everything except "name" should be stripped
+ target: '2695effb5807a22ff3d138d593fd856244e155e7',
+ },
+ ],
+ headers: {
+ [X_TOTAL_HEADER]: 23,
+ },
+ };
+
+ mutations[types.RECEIVE_TAGS_SUCCESS](state, response);
+
+ expect(state.matches.tags).toEqual({
+ list: [
+ {
+ name: 'v1.2',
+ },
+ ],
+ totalCount: 23,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_TAGS_ERROR}`, () => {
+ it('updates state.matches.tags to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.tags = {
+ list: [{ name: 'v1.2' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_TAGS_ERROR](state, error);
+
+ expect(state.matches.tags).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_SUCCESS}`, () => {
+ it('updates state.matches.commits based on the provided API response', () => {
+ const response = {
+ data: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb580',
+ title: 'Initial commit',
+
+ // everything except "id", "short_id", and "title" should be stripped
+ author_name: 'Example User',
+ },
+ };
+
+ mutations[types.RECEIVE_COMMITS_SUCCESS](state, response);
+
+ expect(state.matches.commits).toEqual({
+ list: [
+ {
+ name: '2695effb580',
+ value: '2695effb5807a22ff3d138d593fd856244e155e7',
+ subtitle: 'Initial commit',
+ },
+ ],
+ totalCount: 1,
+ error: null,
+ });
+ });
+ });
+
+ describe(`${types.RECEIVE_COMMITS_ERROR}`, () => {
+ it('updates state.matches.commits to an empty state with the error object', () => {
+ const error = new Error('Something went wrong!');
+
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RECEIVE_COMMITS_ERROR](state, error);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error,
+ });
+ });
+ });
+
+ describe(`${types.RESET_COMMIT_MATCHES}`, () => {
+ it('resets the commit results back to their original (empty) state', () => {
+ state.matches.commits = {
+ list: [{ name: 'abcd0123' }],
+ totalCount: 1,
+ error: null,
+ };
+
+ mutations[types.RESET_COMMIT_MATCHES](state);
+
+ expect(state.matches.commits).toEqual({
+ list: [],
+ totalCount: 0,
+ error: null,
+ });
+ });
+ });
+});