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')
-rw-r--r--spec/frontend/ide/components/commit_sidebar/editor_header_spec.js27
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js210
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js50
-rw-r--r--spec/frontend/logs/mock_data.js70
-rw-r--r--spec/frontend/logs/stores/actions_spec.js332
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js136
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js8
7 files changed, 511 insertions, 322 deletions
diff --git a/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
index 054e7492429..a25aba61516 100644
--- a/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
@@ -14,7 +14,6 @@ describe('IDE commit editor header', () => {
const findDiscardModal = () => wrapper.find({ ref: 'discardModal' });
const findDiscardButton = () => wrapper.find({ ref: 'discardButton' });
- const findActionButton = () => wrapper.find({ ref: 'actionButton' });
beforeEach(() => {
f = file('file');
@@ -28,9 +27,7 @@ describe('IDE commit editor header', () => {
},
});
- jest.spyOn(wrapper.vm, 'stageChange').mockImplementation();
- jest.spyOn(wrapper.vm, 'unstageChange').mockImplementation();
- jest.spyOn(wrapper.vm, 'discardFileChanges').mockImplementation();
+ jest.spyOn(wrapper.vm, 'discardChanges').mockImplementation();
});
afterEach(() => {
@@ -38,8 +35,8 @@ describe('IDE commit editor header', () => {
wrapper = null;
});
- it('renders button to discard & stage', () => {
- expect(wrapper.vm.$el.querySelectorAll('.btn').length).toBe(2);
+ it('renders button to discard', () => {
+ expect(wrapper.vm.$el.querySelectorAll('.btn')).toHaveLength(1);
});
describe('discard button', () => {
@@ -60,23 +57,7 @@ describe('IDE commit editor header', () => {
it('calls discardFileChanges if dialog result is confirmed', () => {
modal.vm.$emit('ok');
- expect(wrapper.vm.discardFileChanges).toHaveBeenCalledWith(f.path);
- });
- });
-
- describe('stage/unstage button', () => {
- it('unstages the file if it was already staged', () => {
- f.staged = true;
-
- findActionButton().trigger('click');
-
- expect(wrapper.vm.unstageChange).toHaveBeenCalledWith(f.path);
- });
-
- it('stages the file if it was not staged', () => {
- findActionButton().trigger('click');
-
- expect(wrapper.vm.stageChange).toHaveBeenCalledWith(f.path);
+ expect(wrapper.vm.discardChanges).toHaveBeenCalledWith(f.path);
});
});
});
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index 26542c3d046..c638b4c05f9 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
+import { GlSprintf, GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import EnvironmentLogs from '~/logs/components/environment_logs.vue';
@@ -20,9 +20,18 @@ import {
jest.mock('~/lib/utils/scroll_utils');
+const module = 'environmentLogs';
+
+jest.mock('lodash/throttle', () =>
+ jest.fn(func => {
+ return func;
+ }),
+);
+
describe('EnvironmentLogs', () => {
let EnvironmentLogsComponent;
let store;
+ let dispatch;
let wrapper;
let state;
@@ -32,14 +41,6 @@ describe('EnvironmentLogs', () => {
clusterApplicationsDocumentationPath: mockDocumentationPath,
};
- const actionMocks = {
- setInitData: jest.fn(),
- setSearch: jest.fn(),
- showPodLogs: jest.fn(),
- showEnvironment: jest.fn(),
- fetchEnvironments: jest.fn(),
- };
-
const updateControlBtnsMock = jest.fn();
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
@@ -47,24 +48,25 @@ describe('EnvironmentLogs', () => {
const findSearchBar = () => wrapper.find('.js-logs-search');
const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' });
const findInfoAlert = () => wrapper.find('.js-elasticsearch-alert');
-
const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
+
+ const findInfiniteScroll = () => wrapper.find({ ref: 'infiniteScroll' });
const findLogTrace = () => wrapper.find('.js-log-trace');
+ const findLogFooter = () => wrapper.find({ ref: 'logFooter' });
+ const getInfiniteScrollAttr = attr => parseInt(findInfiniteScroll().attributes(attr), 10);
const mockSetInitData = () => {
state.pods.options = mockPods;
state.environments.current = mockEnvName;
[state.pods.current] = state.pods.options;
- state.logs.isComplete = false;
- state.logs.lines = mockLogsResult;
+ state.logs.lines = [];
};
- const mockShowPodLogs = podName => {
+ const mockShowPodLogs = () => {
state.pods.options = mockPods;
- [state.pods.current] = podName;
+ [state.pods.current] = mockPods;
- state.logs.isComplete = false;
state.logs.lines = mockLogsResult;
};
@@ -83,10 +85,21 @@ describe('EnvironmentLogs', () => {
methods: {
update: updateControlBtnsMock,
},
+ props: {
+ scrollDownButtonDisabled: false,
+ },
},
- },
- methods: {
- ...actionMocks,
+ GlInfiniteScroll: {
+ name: 'gl-infinite-scroll',
+ template: `
+ <div>
+ <slot name="header"></slot>
+ <slot name="items"></slot>
+ <slot></slot>
+ </div>
+ `,
+ },
+ GlSprintf,
},
});
};
@@ -95,12 +108,14 @@ describe('EnvironmentLogs', () => {
store = createStore();
state = store.state.environmentLogs;
EnvironmentLogsComponent = Vue.extend(EnvironmentLogs);
+
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
+
+ dispatch = store.dispatch;
});
afterEach(() => {
- actionMocks.setInitData.mockReset();
- actionMocks.showPodLogs.mockReset();
- actionMocks.fetchEnvironments.mockReset();
+ store.dispatch.mockReset();
if (wrapper) {
wrapper.destroy();
@@ -124,14 +139,14 @@ describe('EnvironmentLogs', () => {
expect(findTimeRangePicker().is(DateTimePicker)).toBe(true);
// log trace
- expect(findLogTrace().isEmpty()).toBe(false);
+ expect(findInfiniteScroll().exists()).toBe(true);
+ expect(findLogTrace().exists()).toBe(true);
});
it('mounted inits data', () => {
initWrapper();
- expect(actionMocks.setInitData).toHaveBeenCalledTimes(1);
- expect(actionMocks.setInitData).toHaveBeenLastCalledWith({
+ expect(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, {
timeRange: expect.objectContaining({
default: true,
}),
@@ -139,18 +154,15 @@ describe('EnvironmentLogs', () => {
podName: null,
});
- expect(actionMocks.fetchEnvironments).toHaveBeenCalledTimes(1);
- expect(actionMocks.fetchEnvironments).toHaveBeenLastCalledWith(mockEnvironmentsEndpoint);
+ expect(dispatch).toHaveBeenCalledWith(`${module}/fetchEnvironments`, mockEnvironmentsEndpoint);
});
describe('loading state', () => {
beforeEach(() => {
state.pods.options = [];
- state.logs = {
- lines: [],
- isLoading: true,
- };
+ state.logs.lines = [];
+ state.logs.isLoading = true;
state.environments = {
options: [],
@@ -183,6 +195,18 @@ describe('EnvironmentLogs', () => {
expect(updateControlBtnsMock).not.toHaveBeenCalled();
});
+ it('shows an infinite scroll with height and no content', () => {
+ expect(getInfiniteScrollAttr('max-list-height')).toBeGreaterThan(0);
+ expect(getInfiniteScrollAttr('fetched-items')).toBe(0);
+ });
+
+ it('shows an infinite scroll container with equal height and max-height ', () => {
+ const height = getInfiniteScrollAttr('max-list-height');
+
+ expect(height).toEqual(expect.any(Number));
+ expect(findInfiniteScroll().attributes('style')).toMatch(`height: ${height}px;`);
+ });
+
it('shows a logs trace', () => {
expect(findLogTrace().text()).toBe('');
expect(
@@ -193,14 +217,12 @@ describe('EnvironmentLogs', () => {
});
});
- describe('legacy environment', () => {
+ describe('k8s environment', () => {
beforeEach(() => {
state.pods.options = [];
- state.logs = {
- lines: [],
- isLoading: false,
- };
+ state.logs.lines = [];
+ state.logs.isLoading = false;
state.environments = {
options: mockEnvironments,
@@ -226,9 +248,16 @@ describe('EnvironmentLogs', () => {
describe('state with data', () => {
beforeEach(() => {
- actionMocks.setInitData.mockImplementation(mockSetInitData);
- actionMocks.showPodLogs.mockImplementation(mockShowPodLogs);
- actionMocks.fetchEnvironments.mockImplementation(mockFetchEnvs);
+ dispatch.mockImplementation(actionName => {
+ if (actionName === `${module}/setInitData`) {
+ mockSetInitData();
+ } else if (actionName === `${module}/showPodLogs`) {
+ mockShowPodLogs();
+ } else if (actionName === `${module}/fetchEnvironments`) {
+ mockFetchEnvs();
+ mockShowPodLogs();
+ }
+ });
initWrapper();
});
@@ -236,10 +265,6 @@ describe('EnvironmentLogs', () => {
afterEach(() => {
scrollDown.mockReset();
updateControlBtnsMock.mockReset();
-
- actionMocks.setInitData.mockReset();
- actionMocks.showPodLogs.mockReset();
- actionMocks.fetchEnvironments.mockReset();
});
it('displays an enabled search bar', () => {
@@ -249,8 +274,8 @@ describe('EnvironmentLogs', () => {
findSearchBar().vm.$emit('input', mockSearch);
findSearchBar().vm.$emit('submit');
- expect(actionMocks.setSearch).toHaveBeenCalledTimes(1);
- expect(actionMocks.setSearch).toHaveBeenCalledWith(mockSearch);
+ expect(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, expect.any(Object));
+ expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch);
});
it('displays an enabled time window dropdown', () => {
@@ -282,18 +307,21 @@ describe('EnvironmentLogs', () => {
});
});
+ it('shows infinite scroll with height and no content', () => {
+ expect(getInfiniteScrollAttr('max-list-height')).toBeGreaterThan(0);
+ expect(getInfiniteScrollAttr('fetched-items')).toBe(mockTrace.length);
+ });
+
it('populates logs trace', () => {
const trace = findLogTrace();
expect(trace.text().split('\n').length).toBe(mockTrace.length);
expect(trace.text().split('\n')).toEqual(mockTrace);
});
- it('update control buttons state', () => {
- expect(updateControlBtnsMock).toHaveBeenCalledTimes(1);
- });
+ it('populates footer', () => {
+ const footer = findLogFooter().text();
- it('scrolls to bottom when loaded', () => {
- expect(scrollDown).toHaveBeenCalledTimes(1);
+ expect(footer).toContain(`${mockLogsResult.length} results`);
});
describe('when user clicks', () => {
@@ -301,33 +329,99 @@ describe('EnvironmentLogs', () => {
const items = findEnvironmentsDropdown().findAll(GlDropdownItem);
const index = 1; // any env
- expect(actionMocks.showEnvironment).toHaveBeenCalledTimes(0);
+ expect(dispatch).not.toHaveBeenCalledWith(`${module}/showEnvironment`, expect.anything());
items.at(index).vm.$emit('click');
- expect(actionMocks.showEnvironment).toHaveBeenCalledTimes(1);
- expect(actionMocks.showEnvironment).toHaveBeenLastCalledWith(mockEnvironments[index].name);
+ expect(dispatch).toHaveBeenCalledWith(
+ `${module}/showEnvironment`,
+ mockEnvironments[index].name,
+ );
});
it('pod name, trace is refreshed', () => {
const items = findPodsDropdown().findAll(GlDropdownItem);
const index = 2; // any pod
- expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
+ expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
items.at(index).vm.$emit('click');
- expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
- expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPods[index]);
+ expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]);
});
it('refresh button, trace is refreshed', () => {
- expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
+ expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
findLogControlButtons().vm.$emit('refresh');
- expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
- expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPodName);
+ expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPodName);
+ });
+ });
+ });
+
+ describe('listeners', () => {
+ beforeEach(() => {
+ initWrapper();
+ });
+
+ it('attaches listeners in components', () => {
+ expect(findInfiniteScroll().vm.$listeners).toEqual({
+ topReached: expect.any(Function),
+ scroll: expect.any(Function),
+ });
+ });
+
+ it('`topReached` when not loading', () => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(`${module}/fetchMoreLogsPrepend`, undefined);
+
+ findInfiniteScroll().vm.$emit('topReached');
+
+ expect(store.dispatch).toHaveBeenCalledWith(`${module}/fetchMoreLogsPrepend`, undefined);
+ });
+
+ it('`topReached` does not fetches more logs when already loading', () => {
+ state.logs.isLoading = true;
+ findInfiniteScroll().vm.$emit('topReached');
+
+ expect(store.dispatch).not.toHaveBeenCalledWith(`${module}/fetchMoreLogsPrepend`, undefined);
+ });
+
+ it('`topReached` fetches more logs', () => {
+ state.logs.isLoading = true;
+ findInfiniteScroll().vm.$emit('topReached');
+
+ expect(store.dispatch).not.toHaveBeenCalledWith(`${module}/fetchMoreLogsPrepend`, undefined);
+ });
+
+ it('`scroll` on a scrollable target results in enabled scroll buttons', () => {
+ const target = { scrollTop: 10, clientHeight: 10, scrollHeight: 21 };
+
+ state.logs.isLoading = true;
+ findInfiniteScroll().vm.$emit('scroll', { target });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(false);
+ });
+ });
+
+ it('`scroll` on a non-scrollable target in disabled scroll buttons', () => {
+ const target = { scrollTop: 10, clientHeight: 10, scrollHeight: 20 };
+
+ state.logs.isLoading = true;
+ findInfiniteScroll().vm.$emit('scroll', { target });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
+ });
+ });
+
+ it('`scroll` on no target results in disabled scroll buttons', () => {
+ state.logs.isLoading = true;
+ findInfiniteScroll().vm.$emit('scroll', { target: undefined });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
});
});
});
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
index f344e8189c3..38e568f569f 100644
--- a/spec/frontend/logs/components/log_control_buttons_spec.js
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -1,15 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
-import {
- canScroll,
- isScrolledToTop,
- isScrolledToBottom,
- scrollDown,
- scrollUp,
-} from '~/lib/utils/scroll_utils';
-
-jest.mock('~/lib/utils/scroll_utils');
describe('LogControlButtons', () => {
let wrapper;
@@ -18,8 +9,14 @@ describe('LogControlButtons', () => {
const findScrollToBottom = () => wrapper.find('.js-scroll-to-bottom');
const findRefreshBtn = () => wrapper.find('.js-refresh-log');
- const initWrapper = () => {
- wrapper = shallowMount(LogControlButtons);
+ const initWrapper = opts => {
+ wrapper = shallowMount(LogControlButtons, {
+ listeners: {
+ scrollUp: () => {},
+ scrollDown: () => {},
+ },
+ ...opts,
+ });
};
afterEach(() => {
@@ -55,27 +52,16 @@ describe('LogControlButtons', () => {
describe('when scrolling actions are enabled', () => {
beforeEach(() => {
// mock scrolled to the middle of a long page
- canScroll.mockReturnValue(true);
- isScrolledToBottom.mockReturnValue(false);
- isScrolledToTop.mockReturnValue(false);
-
initWrapper();
- wrapper.vm.update();
return wrapper.vm.$nextTick();
});
- afterEach(() => {
- canScroll.mockReset();
- isScrolledToTop.mockReset();
- isScrolledToBottom.mockReset();
- });
-
it('click on "scroll to top" scrolls up', () => {
expect(findScrollToTop().is('[disabled]')).toBe(false);
findScrollToTop().vm.$emit('click');
- expect(scrollUp).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted('scrollUp')).toHaveLength(1);
});
it('click on "scroll to bottom" scrolls down', () => {
@@ -83,25 +69,23 @@ describe('LogControlButtons', () => {
findScrollToBottom().vm.$emit('click');
- expect(scrollDown).toHaveBeenCalledTimes(1); // plus one time when trace was loaded
+ expect(wrapper.emitted('scrollDown')).toHaveLength(1);
});
});
describe('when scrolling actions are disabled', () => {
beforeEach(() => {
- // mock a short page without a scrollbar
- canScroll.mockReturnValue(false);
- isScrolledToBottom.mockReturnValue(true);
- isScrolledToTop.mockReturnValue(true);
-
- initWrapper();
+ initWrapper({ listeners: {} });
+ return wrapper.vm.$nextTick();
});
it('buttons are disabled', () => {
- wrapper.vm.update();
return wrapper.vm.$nextTick(() => {
- expect(findScrollToTop().is('[disabled]')).toBe(true);
- expect(findScrollToBottom().is('[disabled]')).toBe(true);
+ expect(findScrollToTop().exists()).toBe(false);
+ expect(findScrollToBottom().exists()).toBe(false);
+ // This should be enabled when gitlab-ui contains:
+ // https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1149
+ // expect(findScrollToBottom().is('[disabled]')).toBe(true);
});
});
});
diff --git a/spec/frontend/logs/mock_data.js b/spec/frontend/logs/mock_data.js
index 4c092a84b36..1a84d6edd12 100644
--- a/spec/frontend/logs/mock_data.js
+++ b/spec/frontend/logs/mock_data.js
@@ -1,14 +1,18 @@
-export const mockProjectPath = 'root/autodevops-deploy';
+const mockProjectPath = 'root/autodevops-deploy';
+
export const mockEnvName = 'production';
export const mockEnvironmentsEndpoint = `${mockProjectPath}/environments.json`;
export const mockEnvId = '99';
export const mockDocumentationPath = '/documentation.md';
+export const mockLogsEndpoint = '/dummy_logs_path.json';
+export const mockCursor = 'MOCK_CURSOR';
+export const mockNextCursor = 'MOCK_NEXT_CURSOR';
const makeMockEnvironment = (id, name, advancedQuerying) => ({
id,
project_path: mockProjectPath,
name,
- logs_api_path: '/dummy_logs_path.json',
+ logs_api_path: mockLogsEndpoint,
enable_advanced_logs_querying: advancedQuerying,
});
@@ -28,58 +32,22 @@ export const mockPods = [
];
export const mockLogsResult = [
- {
- timestamp: '2019-12-13T13:43:18.2760123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:29:48 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:18.2760123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:26.8420123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:29:57 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:26.8420123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:28.3710123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:29:58 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:28.3710123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:36.8860123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:30:07 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:36.8860123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:38.4000123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:30:08 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:38.4000123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:46.8420123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:30:17 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:46.8430123Z', message: '- -> /' },
- {
- timestamp: '2019-12-13T13:43:48.3240123Z',
- message: '10.36.0.1 - - [16/Oct/2019:06:30:18 UTC] "GET / HTTP/1.1" 200 13',
- },
- { timestamp: '2019-12-13T13:43:48.3250123Z', message: '- -> /' },
+ { timestamp: '2019-12-13T13:43:18.2760123Z', message: 'Log 1' },
+ { timestamp: '2019-12-13T13:43:18.2760123Z', message: 'Log 2' },
+ { timestamp: '2019-12-13T13:43:26.8420123Z', message: 'Log 3' },
];
export const mockTrace = [
- 'Dec 13 13:43:18.276Z | 10.36.0.1 - - [16/Oct/2019:06:29:48 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:18.276Z | - -> /',
- 'Dec 13 13:43:26.842Z | 10.36.0.1 - - [16/Oct/2019:06:29:57 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:26.842Z | - -> /',
- 'Dec 13 13:43:28.371Z | 10.36.0.1 - - [16/Oct/2019:06:29:58 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:28.371Z | - -> /',
- 'Dec 13 13:43:36.886Z | 10.36.0.1 - - [16/Oct/2019:06:30:07 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:36.886Z | - -> /',
- 'Dec 13 13:43:38.400Z | 10.36.0.1 - - [16/Oct/2019:06:30:08 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:38.400Z | - -> /',
- 'Dec 13 13:43:46.842Z | 10.36.0.1 - - [16/Oct/2019:06:30:17 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:46.843Z | - -> /',
- 'Dec 13 13:43:48.324Z | 10.36.0.1 - - [16/Oct/2019:06:30:18 UTC] "GET / HTTP/1.1" 200 13',
- 'Dec 13 13:43:48.325Z | - -> /',
+ 'Dec 13 13:43:18.276Z | Log 1',
+ 'Dec 13 13:43:18.276Z | Log 2',
+ 'Dec 13 13:43:26.842Z | Log 3',
];
+export const mockResponse = {
+ pod_name: mockPodName,
+ pods: mockPods,
+ logs: mockLogsResult,
+ cursor: mockNextCursor,
+};
+
export const mockSearch = 'foo +bar';
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
index 6309126159e..1512797e1bc 100644
--- a/spec/frontend/logs/stores/actions_spec.js
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -10,6 +10,7 @@ import {
showPodLogs,
fetchEnvironments,
fetchLogs,
+ fetchMoreLogsPrepend,
} from '~/logs/stores/actions';
import { defaultTimeRange } from '~/monitoring/constants';
@@ -18,7 +19,6 @@ import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import {
- mockProjectPath,
mockPodName,
mockEnvironmentsEndpoint,
mockEnvironments,
@@ -26,6 +26,10 @@ import {
mockLogsResult,
mockEnvName,
mockSearch,
+ mockLogsEndpoint,
+ mockResponse,
+ mockCursor,
+ mockNextCursor,
} from '../mock_data';
jest.mock('~/flash');
@@ -52,6 +56,8 @@ describe('Logs Store actions', () => {
let state;
let mock;
+ const latestGetParams = () => mock.history.get[mock.history.get.length - 1].params;
+
convertToFixedRange.mockImplementation(range => {
if (range === defaultTimeRange) {
return { ...mockDefaultRange };
@@ -75,10 +81,16 @@ describe('Logs Store actions', () => {
describe('setInitData', () => {
it('should commit environment and pod name mutation', () =>
- testAction(setInitData, { environmentName: mockEnvName, podName: mockPodName }, state, [
- { type: types.SET_PROJECT_ENVIRONMENT, payload: mockEnvName },
- { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
- ]));
+ testAction(
+ setInitData,
+ { timeRange: mockFixedRange, environmentName: mockEnvName, podName: mockPodName },
+ state,
+ [
+ { type: types.SET_TIME_RANGE, payload: mockFixedRange },
+ { type: types.SET_PROJECT_ENVIRONMENT, payload: mockEnvName },
+ { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
+ ],
+ ));
});
describe('setSearch', () => {
@@ -140,183 +152,245 @@ describe('Logs Store actions', () => {
});
});
- describe('fetchLogs', () => {
+ describe('when the backend responds succesfully', () => {
+ let expectedMutations;
+ let expectedActions;
+
beforeEach(() => {
mock = new MockAdapter(axios);
+ mock.onGet(mockLogsEndpoint).reply(200, mockResponse);
+ mock.onGet(mockLogsEndpoint).replyOnce(202); // mock reactive cache
+
+ state.environments.options = mockEnvironments;
+ state.environments.current = mockEnvName;
});
afterEach(() => {
mock.reset();
});
- it('should commit logs and pod data when there is pod name defined', () => {
- state.environments.options = mockEnvironments;
- state.environments.current = mockEnvName;
- state.pods.current = mockPodName;
-
- const endpoint = '/dummy_logs_path.json';
-
- mock
- .onGet(endpoint, {
- params: {
- pod_name: mockPodName,
- ...mockDefaultRange,
- },
- })
- .reply(200, {
- pod_name: mockPodName,
- pods: mockPods,
- logs: mockLogsResult,
- });
-
- mock.onGet(endpoint).replyOnce(202); // mock reactive cache
-
- return testAction(
- fetchLogs,
- null,
- state,
- [
+ describe('fetchLogs', () => {
+ beforeEach(() => {
+ expectedMutations = [
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
- { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
- ],
- [],
- );
- });
+ {
+ type: types.RECEIVE_LOGS_DATA_SUCCESS,
+ payload: { logs: mockLogsResult, cursor: mockNextCursor },
+ },
+ ];
- it('should commit logs and pod data when there is pod name defined and a non-default date range', () => {
- state.projectPath = mockProjectPath;
- state.environments.options = mockEnvironments;
- state.environments.current = mockEnvName;
- state.pods.current = mockPodName;
- state.timeRange.current = mockFixedRange;
+ expectedActions = [];
+ });
- const endpoint = '/dummy_logs_path.json';
+ it('should commit logs and pod data when there is pod name defined', () => {
+ state.pods.current = mockPodName;
- mock
- .onGet(endpoint, {
- params: {
+ return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
+ expect(latestGetParams()).toMatchObject({
pod_name: mockPodName,
- start: mockFixedRange.start,
- end: mockFixedRange.end,
- },
- })
- .reply(200, {
- pod_name: mockPodName,
- pods: mockPods,
- logs: mockLogsResult,
+ });
});
+ });
- return testAction(
- fetchLogs,
- null,
- state,
- [
- { type: types.REQUEST_PODS_DATA },
- { type: types.REQUEST_LOGS_DATA },
- { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
- { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
- { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
- ],
- [],
- );
- });
-
- it('should commit logs and pod data when there is pod name and search and a faulty date range', () => {
- state.environments.options = mockEnvironments;
- state.environments.current = mockEnvName;
- state.pods.current = mockPodName;
- state.search = mockSearch;
- state.timeRange.current = 'INVALID_TIME_RANGE';
-
- const endpoint = '/dummy_logs_path.json';
+ it('should commit logs and pod data when there is pod name defined and a non-default date range', () => {
+ state.pods.current = mockPodName;
+ state.timeRange.current = mockFixedRange;
+ state.logs.cursor = mockCursor;
- mock
- .onGet(endpoint, {
- params: {
+ return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
+ expect(latestGetParams()).toEqual({
pod_name: mockPodName,
- search: mockSearch,
- },
- })
- .reply(200, {
- pod_name: mockPodName,
- pods: mockPods,
- logs: mockLogsResult,
+ start: mockFixedRange.start,
+ end: mockFixedRange.end,
+ cursor: mockCursor,
+ });
});
+ });
- mock.onGet(endpoint).replyOnce(202); // mock reactive cache
+ it('should commit logs and pod data when there is pod name and search and a faulty date range', () => {
+ state.pods.current = mockPodName;
+ state.search = mockSearch;
+ state.timeRange.current = 'INVALID_TIME_RANGE';
- return testAction(
- fetchLogs,
- null,
- state,
- [
- { type: types.REQUEST_PODS_DATA },
- { type: types.REQUEST_LOGS_DATA },
- { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
- { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
- { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
- ],
- [],
- () => {
+ return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
+ expect(latestGetParams()).toEqual({
+ pod_name: mockPodName,
+ search: mockSearch,
+ });
// Warning about time ranges was issued
expect(flash).toHaveBeenCalledTimes(1);
expect(flash).toHaveBeenCalledWith(expect.any(String), 'warning');
- },
- );
+ });
+ });
+
+ it('should commit logs and pod data when no pod name defined', () => {
+ state.timeRange.current = mockDefaultRange;
+
+ return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
+ expect(latestGetParams()).toEqual({});
+ });
+ });
});
- it('should commit logs and pod data when no pod name defined', done => {
- state.environments.options = mockEnvironments;
- state.environments.current = mockEnvName;
+ describe('fetchMoreLogsPrepend', () => {
+ beforeEach(() => {
+ expectedMutations = [
+ { type: types.REQUEST_LOGS_DATA_PREPEND },
+ {
+ type: types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS,
+ payload: { logs: mockLogsResult, cursor: mockNextCursor },
+ },
+ ];
- const endpoint = '/dummy_logs_path.json';
+ expectedActions = [];
+ });
- mock.onGet(endpoint, { params: { ...mockDefaultRange } }).reply(200, {
- pod_name: mockPodName,
- pods: mockPods,
- logs: mockLogsResult,
+ it('should commit logs and pod data when there is pod name defined', () => {
+ state.pods.current = mockPodName;
+
+ expectedActions = [];
+
+ return testAction(
+ fetchMoreLogsPrepend,
+ null,
+ state,
+ expectedMutations,
+ expectedActions,
+ () => {
+ expect(latestGetParams()).toMatchObject({
+ pod_name: mockPodName,
+ });
+ },
+ );
});
- mock.onGet(endpoint).replyOnce(202); // mock reactive cache
- testAction(
+ it('should commit logs and pod data when there is pod name defined and a non-default date range', () => {
+ state.pods.current = mockPodName;
+ state.timeRange.current = mockFixedRange;
+ state.logs.cursor = mockCursor;
+
+ return testAction(
+ fetchMoreLogsPrepend,
+ null,
+ state,
+ expectedMutations,
+ expectedActions,
+ () => {
+ expect(latestGetParams()).toEqual({
+ pod_name: mockPodName,
+ start: mockFixedRange.start,
+ end: mockFixedRange.end,
+ cursor: mockCursor,
+ });
+ },
+ );
+ });
+
+ it('should commit logs and pod data when there is pod name and search and a faulty date range', () => {
+ state.pods.current = mockPodName;
+ state.search = mockSearch;
+ state.timeRange.current = 'INVALID_TIME_RANGE';
+
+ return testAction(
+ fetchMoreLogsPrepend,
+ null,
+ state,
+ expectedMutations,
+ expectedActions,
+ () => {
+ expect(latestGetParams()).toEqual({
+ pod_name: mockPodName,
+ search: mockSearch,
+ });
+ // Warning about time ranges was issued
+ expect(flash).toHaveBeenCalledTimes(1);
+ expect(flash).toHaveBeenCalledWith(expect.any(String), 'warning');
+ },
+ );
+ });
+
+ it('should commit logs and pod data when no pod name defined', () => {
+ state.timeRange.current = mockDefaultRange;
+
+ return testAction(
+ fetchMoreLogsPrepend,
+ null,
+ state,
+ expectedMutations,
+ expectedActions,
+ () => {
+ expect(latestGetParams()).toEqual({});
+ },
+ );
+ });
+
+ it('should not commit logs or pod data when it has reached the end', () => {
+ state.logs.isComplete = true;
+ state.logs.cursor = null;
+
+ return testAction(
+ fetchMoreLogsPrepend,
+ null,
+ state,
+ [], // no mutations done
+ [], // no actions dispatched
+ () => {
+ expect(mock.history.get).toHaveLength(0);
+ },
+ );
+ });
+ });
+ });
+
+ describe('when the backend responds with an error', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet(mockLogsEndpoint).reply(500);
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ it('fetchLogs should commit logs and pod errors', () => {
+ state.environments.options = mockEnvironments;
+ state.environments.current = mockEnvName;
+
+ return testAction(
fetchLogs,
null,
state,
[
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
- { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
- { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
- { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
+ { type: types.RECEIVE_PODS_DATA_ERROR },
+ { type: types.RECEIVE_LOGS_DATA_ERROR },
],
[],
- done,
+ () => {
+ expect(mock.history.get[0].url).toBe(mockLogsEndpoint);
+ },
);
});
- it('should commit logs and pod errors when backend fails', () => {
+ it('fetchMoreLogsPrepend should commit logs and pod errors', () => {
state.environments.options = mockEnvironments;
state.environments.current = mockEnvName;
- const endpoint = `/${mockProjectPath}/-/logs/elasticsearch.json?environment_name=${mockEnvName}`;
- mock.onGet(endpoint).replyOnce(500);
-
return testAction(
- fetchLogs,
+ fetchMoreLogsPrepend,
null,
state,
[
- { type: types.REQUEST_PODS_DATA },
- { type: types.REQUEST_LOGS_DATA },
- { type: types.RECEIVE_PODS_DATA_ERROR },
- { type: types.RECEIVE_LOGS_DATA_ERROR },
+ { type: types.REQUEST_LOGS_DATA_PREPEND },
+ { type: types.RECEIVE_LOGS_DATA_PREPEND_ERROR },
],
[],
() => {
- expect(flash).toHaveBeenCalledTimes(1);
+ expect(mock.history.get[0].url).toBe(mockLogsEndpoint);
},
);
});
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
index dcb358c7d5b..eae838a31d4 100644
--- a/spec/frontend/logs/stores/mutations_spec.js
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -9,6 +9,8 @@ import {
mockPodName,
mockLogsResult,
mockSearch,
+ mockCursor,
+ mockNextCursor,
} from '../mock_data';
describe('Logs Store Mutations', () => {
@@ -73,27 +75,47 @@ describe('Logs Store Mutations', () => {
it('starts loading for logs', () => {
mutations[types.REQUEST_LOGS_DATA](state);
- expect(state.logs).toEqual(
- expect.objectContaining({
- lines: [],
- isLoading: true,
- isComplete: false,
- }),
- );
+ expect(state.timeRange.current).toEqual({
+ start: expect.any(String),
+ end: expect.any(String),
+ });
+
+ expect(state.logs).toEqual({
+ lines: [],
+ cursor: null,
+ isLoading: true,
+ isComplete: false,
+ });
});
});
describe('RECEIVE_LOGS_DATA_SUCCESS', () => {
- it('receives logs lines', () => {
- mutations[types.RECEIVE_LOGS_DATA_SUCCESS](state, mockLogsResult);
+ it('receives logs lines and cursor', () => {
+ mutations[types.RECEIVE_LOGS_DATA_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: mockCursor,
+ });
- expect(state.logs).toEqual(
- expect.objectContaining({
- lines: mockLogsResult,
- isLoading: false,
- isComplete: true,
- }),
- );
+ expect(state.logs).toEqual({
+ lines: mockLogsResult,
+ isLoading: false,
+ cursor: mockCursor,
+ isComplete: false,
+ });
+ });
+
+ it('receives logs lines and a null cursor to indicate the end', () => {
+ mutations[types.RECEIVE_LOGS_DATA_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: null,
+ });
+
+ expect(state.logs).toEqual({
+ lines: mockLogsResult,
+ isLoading: false,
+ cursor: null,
+ isComplete: true,
+ });
});
});
@@ -101,13 +123,77 @@ describe('Logs Store Mutations', () => {
it('receives log data error and stops loading', () => {
mutations[types.RECEIVE_LOGS_DATA_ERROR](state);
- expect(state.logs).toEqual(
- expect.objectContaining({
- lines: [],
- isLoading: false,
- isComplete: true,
- }),
- );
+ expect(state.logs).toEqual({
+ lines: [],
+ isLoading: false,
+ cursor: null,
+ isComplete: false,
+ });
+ });
+ });
+
+ describe('REQUEST_LOGS_DATA_PREPEND', () => {
+ it('receives logs lines and cursor', () => {
+ mutations[types.REQUEST_LOGS_DATA_PREPEND](state);
+
+ expect(state.logs.isLoading).toBe(true);
+ });
+ });
+
+ describe('RECEIVE_LOGS_DATA_PREPEND_SUCCESS', () => {
+ it('receives logs lines and cursor', () => {
+ mutations[types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: mockCursor,
+ });
+
+ expect(state.logs).toEqual({
+ lines: mockLogsResult,
+ isLoading: false,
+ cursor: mockCursor,
+ isComplete: false,
+ });
+ });
+
+ it('receives additional logs lines and a new cursor', () => {
+ mutations[types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: mockCursor,
+ });
+
+ mutations[types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: mockNextCursor,
+ });
+
+ expect(state.logs).toEqual({
+ lines: [...mockLogsResult, ...mockLogsResult],
+ isLoading: false,
+ cursor: mockNextCursor,
+ isComplete: false,
+ });
+ });
+
+ it('receives logs lines and a null cursor to indicate is complete', () => {
+ mutations[types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS](state, {
+ logs: mockLogsResult,
+ cursor: null,
+ });
+
+ expect(state.logs).toEqual({
+ lines: mockLogsResult,
+ isLoading: false,
+ cursor: null,
+ isComplete: true,
+ });
+ });
+ });
+
+ describe('RECEIVE_LOGS_DATA_PREPEND_ERROR', () => {
+ it('receives logs lines and cursor', () => {
+ mutations[types.RECEIVE_LOGS_DATA_PREPEND_ERROR](state);
+
+ expect(state.logs.isLoading).toBe(false);
});
});
@@ -121,6 +207,7 @@ describe('Logs Store Mutations', () => {
describe('SET_TIME_RANGE', () => {
it('sets a default range', () => {
+ expect(state.timeRange.selected).toEqual(expect.any(Object));
expect(state.timeRange.current).toEqual(expect.any(Object));
});
@@ -131,12 +218,13 @@ describe('Logs Store Mutations', () => {
};
mutations[types.SET_TIME_RANGE](state, mockRange);
+ expect(state.timeRange.selected).toEqual(mockRange);
expect(state.timeRange.current).toEqual(mockRange);
});
});
describe('REQUEST_PODS_DATA', () => {
- it('receives log data error and stops loading', () => {
+ it('receives pods data', () => {
mutations[types.REQUEST_PODS_DATA](state);
expect(state.pods).toEqual(
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
index 8258eb8204c..b77116be464 100644
--- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -54,10 +54,10 @@ describe('Changed file icon', () => {
});
describe.each`
- file | iconName | tooltipText | desc
- ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'}
- ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'}
- ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'}
+ file | iconName | tooltipText | desc
+ ${changedFile()} | ${'file-modified'} | ${'Modified'} | ${'with file changed'}
+ ${stagedFile()} | ${'file-modified-solid'} | ${'Modified'} | ${'with file staged'}
+ ${newFile()} | ${'file-addition'} | ${'Added'} | ${'with file new'}
`('$desc', ({ file, iconName, tooltipText }) => {
beforeEach(() => {
factory({ file });