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/notes')
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js41
-rw-r--r--spec/frontend/notes/components/discussion_actions_spec.js11
-rw-r--r--spec/frontend/notes/components/discussion_reply_placeholder_spec.js23
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js12
-rw-r--r--spec/frontend/notes/stores/actions_spec.js180
5 files changed, 211 insertions, 56 deletions
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index b140eea9439..537622b7918 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -328,20 +328,45 @@ describe('issue_comment_form component', () => {
mountComponent({ mountFunction: mount });
});
- it('should save note when cmd+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSave');
+ describe('when no draft exists', () => {
+ it('should save note when cmd+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSave');
- findTextArea().trigger('keydown.enter', { metaKey: true });
+ findTextArea().trigger('keydown.enter', { metaKey: true });
- expect(wrapper.vm.handleSave).toHaveBeenCalled();
+ expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
+ });
+
+ it('should save note when ctrl+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSave');
+
+ findTextArea().trigger('keydown.enter', { ctrlKey: true });
+
+ expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
+ });
});
- it('should save note when ctrl+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSave');
+ describe('when a draft exists', () => {
+ beforeEach(() => {
+ store.registerModule('batchComments', batchComments());
+ store.state.batchComments.drafts = [{ note: 'A' }];
+ });
+
+ it('should save note draft when cmd+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSaveDraft');
+
+ findTextArea().trigger('keydown.enter', { metaKey: true });
+
+ expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
+ });
+
+ it('should save note draft when ctrl+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSaveDraft');
- findTextArea().trigger('keydown.enter', { ctrlKey: true });
+ findTextArea().trigger('keydown.enter', { ctrlKey: true });
- expect(wrapper.vm.handleSave).toHaveBeenCalled();
+ expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
+ });
});
});
});
diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js
index c6a7d7ead98..925dbcc09ec 100644
--- a/spec/frontend/notes/components/discussion_actions_spec.js
+++ b/spec/frontend/notes/components/discussion_actions_spec.js
@@ -20,7 +20,7 @@ const createUnallowedNote = () =>
describe('DiscussionActions', () => {
let wrapper;
- const createComponentFactory = (shallow = true) => (props) => {
+ const createComponentFactory = (shallow = true) => (props, options) => {
const store = createStore();
const mountFn = shallow ? shallowMount : mount;
@@ -34,6 +34,7 @@ describe('DiscussionActions', () => {
shouldShowJumpToNextDiscussion: true,
...props,
},
+ ...options,
});
};
@@ -90,17 +91,17 @@ describe('DiscussionActions', () => {
describe('events handling', () => {
const createComponent = createComponentFactory(false);
- beforeEach(() => {
- createComponent();
- });
-
it('emits showReplyForm event when clicking on reply placeholder', () => {
+ createComponent({}, { attachTo: document.body });
+
jest.spyOn(wrapper.vm, '$emit');
wrapper.find(ReplyPlaceholder).find('textarea').trigger('focus');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('showReplyForm');
});
it('emits resolve event when clicking on resolve button', () => {
+ createComponent();
+
jest.spyOn(wrapper.vm, '$emit');
wrapper.find(ResolveDiscussionButton).find('button').trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('resolve');
diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
index 2a4cd0df0c7..3932f818c4e 100644
--- a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
+++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
@@ -6,31 +6,34 @@ const placeholderText = 'Test Button Text';
describe('ReplyPlaceholder', () => {
let wrapper;
- const findTextarea = () => wrapper.find({ ref: 'textarea' });
-
- beforeEach(() => {
+ const createComponent = ({ options = {} } = {}) => {
wrapper = shallowMount(ReplyPlaceholder, {
propsData: {
placeholderText,
},
+ ...options,
});
- });
+ };
+
+ const findTextarea = () => wrapper.find({ ref: 'textarea' });
afterEach(() => {
wrapper.destroy();
});
- it('emits focus event on button click', () => {
- findTextarea().trigger('focus');
+ it('emits focus event on button click', async () => {
+ createComponent({ options: { attachTo: document.body } });
+
+ await findTextarea().trigger('focus');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted()).toEqual({
- focus: [[]],
- });
+ expect(wrapper.emitted()).toEqual({
+ focus: [[]],
});
});
it('should render reply button', () => {
+ createComponent();
+
expect(findTextarea().attributes('placeholder')).toEqual(placeholderText);
});
});
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index 735bc2b70dd..a364a524e7b 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -56,6 +56,18 @@ describe('noteable_discussion component', () => {
expect(wrapper.find('.discussion-header').exists()).toBe(true);
});
+ it('should hide actions when diff refs do not exists', async () => {
+ const discussion = { ...discussionMock };
+ discussion.diff_file = { ...mockDiffFile, diff_refs: null };
+ discussion.diff_discussion = true;
+ discussion.expanded = false;
+
+ wrapper.setProps({ discussion });
+ await nextTick();
+
+ expect(wrapper.vm.canShowReplyActions).toBe(false);
+ });
+
describe('actions', () => {
it('should toggle reply form', async () => {
await nextTick();
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 9b7456d54bc..7eef2017dfb 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -25,7 +25,19 @@ import {
} from '../mock_data';
const TEST_ERROR_MESSAGE = 'Test error message';
-jest.mock('~/flash');
+const mockFlashClose = jest.fn();
+jest.mock('~/flash', () => {
+ const flash = jest.fn().mockImplementation(() => {
+ return {
+ close: mockFlashClose,
+ };
+ });
+
+ return {
+ createFlash: flash,
+ deprecatedCreateFlash: flash,
+ };
+});
describe('Actions Notes Store', () => {
let commit;
@@ -254,42 +266,144 @@ describe('Actions Notes Store', () => {
});
describe('poll', () => {
- beforeEach((done) => {
- axiosMock
- .onGet(notesDataMock.notesPath)
- .reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
+ const pollInterval = 6000;
+ const pollResponse = { notes: [], last_fetched_at: '123456' };
+ const pollHeaders = { 'poll-interval': `${pollInterval}` };
+ const successMock = () =>
+ axiosMock.onGet(notesDataMock.notesPath).reply(200, pollResponse, pollHeaders);
+ const failureMock = () => axiosMock.onGet(notesDataMock.notesPath).reply(500);
+ const advanceAndRAF = async (time) => {
+ if (time) {
+ jest.advanceTimersByTime(time);
+ }
+
+ return new Promise((resolve) => requestAnimationFrame(resolve));
+ };
+ const advanceXMoreIntervals = async (number) => {
+ const timeoutLength = pollInterval * number;
+ return advanceAndRAF(timeoutLength);
+ };
+ const startPolling = async () => {
+ await store.dispatch('poll');
+ await advanceAndRAF(2);
+ };
+ const cleanUp = async () => {
+ jest.clearAllTimers();
+
+ return store.dispatch('stopPolling');
+ };
+
+ beforeEach((done) => {
store.dispatch('setNotesData', notesDataMock).then(done).catch(done.fail);
});
- it('calls service with last fetched state', (done) => {
- store
- .dispatch('poll')
- .then(() => {
- jest.advanceTimersByTime(2);
- })
- .then(() => new Promise((resolve) => requestAnimationFrame(resolve)))
- .then(() => {
- expect(store.state.lastFetchedAt).toBe('123456');
-
- jest.advanceTimersByTime(1500);
- })
- .then(
- () =>
- new Promise((resolve) => {
- requestAnimationFrame(resolve);
- }),
- )
- .then(() => {
- const expectedGetRequests = 2;
- expect(axiosMock.history.get.length).toBe(expectedGetRequests);
- expect(axiosMock.history.get[expectedGetRequests - 1].headers).toMatchObject({
- 'X-Last-Fetched-At': '123456',
- });
- })
- .then(() => store.dispatch('stopPolling'))
- .then(done)
- .catch(done.fail);
+ afterEach(() => {
+ return cleanUp();
+ });
+
+ it('calls service with last fetched state', async () => {
+ successMock();
+
+ await startPolling();
+
+ expect(store.state.lastFetchedAt).toBe('123456');
+
+ await advanceXMoreIntervals(1);
+
+ expect(axiosMock.history.get).toHaveLength(2);
+ expect(axiosMock.history.get[1].headers).toMatchObject({
+ 'X-Last-Fetched-At': '123456',
+ });
+ });
+
+ describe('polling side effects', () => {
+ it('retries twice', async () => {
+ failureMock();
+
+ await startPolling();
+
+ // This is the first request, not a retry
+ expect(axiosMock.history.get).toHaveLength(1);
+
+ await advanceXMoreIntervals(1);
+
+ // Retry #1
+ expect(axiosMock.history.get).toHaveLength(2);
+
+ await advanceXMoreIntervals(1);
+
+ // Retry #2
+ expect(axiosMock.history.get).toHaveLength(3);
+
+ await advanceXMoreIntervals(10);
+
+ // There are no more retries
+ expect(axiosMock.history.get).toHaveLength(3);
+ });
+
+ it('shows the error display on the second failure', async () => {
+ failureMock();
+
+ await startPolling();
+
+ expect(axiosMock.history.get).toHaveLength(1);
+ expect(Flash).not.toHaveBeenCalled();
+
+ await advanceXMoreIntervals(1);
+
+ expect(axiosMock.history.get).toHaveLength(2);
+ expect(Flash).toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledTimes(1);
+ });
+
+ it('resets the failure counter on success', async () => {
+ // We can't get access to the actual counter in the polling closure.
+ // So we can infer that it's reset by ensuring that the error is only
+ // shown when we cause two failures in a row - no successes between
+
+ axiosMock
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(500) // cause one error
+ .onGet(notesDataMock.notesPath)
+ .replyOnce(200, pollResponse, pollHeaders) // then a success
+ .onGet(notesDataMock.notesPath)
+ .reply(500); // and then more errors
+
+ await startPolling(); // Failure #1
+ await advanceXMoreIntervals(1); // Success #1
+ await advanceXMoreIntervals(1); // Failure #2
+
+ // That was the first failure AFTER a success, so we should NOT see the error displayed
+ expect(Flash).not.toHaveBeenCalled();
+
+ // Now we'll allow another failure
+ await advanceXMoreIntervals(1); // Failure #3
+
+ // Since this is the second failure in a row, the error should happen
+ expect(Flash).toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledTimes(1);
+ });
+
+ it('hides the error display if it exists on success', async () => {
+ jest.mock();
+ failureMock();
+
+ await startPolling();
+ await advanceXMoreIntervals(2);
+
+ // After two errors, the error should be displayed
+ expect(Flash).toHaveBeenCalled();
+ expect(Flash).toHaveBeenCalledTimes(1);
+
+ axiosMock.reset();
+ successMock();
+
+ await advanceXMoreIntervals(1);
+
+ expect(mockFlashClose).toHaveBeenCalled();
+ expect(mockFlashClose).toHaveBeenCalledTimes(1);
+ });
});
});