summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-11 15:09:37 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-11 15:09:37 +0000
commita210c43e0aca0311cc1d3d381763b25979ec72dc (patch)
tree0325d173da7a6e7bd6c2cdf450d0aa1c4e142d0f /spec/frontend
parentc9687bdf58e9d4a9c3942f587bd4841f42e3b5de (diff)
downloadgitlab-ce-a210c43e0aca0311cc1d3d381763b25979ec72dc.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/blob/sketch/index_spec.js92
-rw-r--r--spec/frontend/clusters_list/mock_data.js5
-rw-r--r--spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js142
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js334
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js108
-rw-r--r--spec/frontend/logs/mock_data.js85
-rw-r--r--spec/frontend/logs/stores/actions_spec.js324
-rw-r--r--spec/frontend/logs/stores/getters_spec.js40
-rw-r--r--spec/frontend/logs/stores/mutations_spec.js171
-rw-r--r--spec/frontend/logs/utils_spec.js38
10 files changed, 1278 insertions, 61 deletions
diff --git a/spec/frontend/blob/sketch/index_spec.js b/spec/frontend/blob/sketch/index_spec.js
new file mode 100644
index 00000000000..f5e9da21b2a
--- /dev/null
+++ b/spec/frontend/blob/sketch/index_spec.js
@@ -0,0 +1,92 @@
+import JSZip from 'jszip';
+import SketchLoader from '~/blob/sketch';
+
+jest.mock('jszip');
+
+describe('Sketch viewer', () => {
+ preloadFixtures('static/sketch_viewer.html');
+
+ beforeEach(() => {
+ loadFixtures('static/sketch_viewer.html');
+ window.URL = {
+ createObjectURL: jest.fn(() => 'http://foo/bar'),
+ };
+ });
+
+ afterEach(() => {
+ window.URL = {};
+ });
+
+ describe('with error message', () => {
+ beforeEach(done => {
+ jest.spyOn(SketchLoader.prototype, 'getZipFile').mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ reject();
+ done();
+ }),
+ );
+
+ return new SketchLoader(document.getElementById('js-sketch-viewer'));
+ });
+
+ it('renders error message', () => {
+ expect(document.querySelector('#js-sketch-viewer p')).not.toBeNull();
+
+ expect(document.querySelector('#js-sketch-viewer p').textContent.trim()).toContain(
+ 'Cannot show preview.',
+ );
+ });
+
+ it('removes the loading icon', () => {
+ expect(document.querySelector('.js-loading-icon')).toBeNull();
+ });
+ });
+
+ describe('success', () => {
+ beforeEach(done => {
+ const loadAsyncMock = {
+ files: {
+ 'previews/preview.png': {
+ async: jest.fn(),
+ },
+ },
+ };
+
+ loadAsyncMock.files['previews/preview.png'].async.mockImplementation(
+ () =>
+ new Promise(resolve => {
+ resolve('foo');
+ done();
+ }),
+ );
+
+ jest.spyOn(SketchLoader.prototype, 'getZipFile').mockResolvedValue();
+ jest.spyOn(JSZip, 'loadAsync').mockResolvedValue(loadAsyncMock);
+ return new SketchLoader(document.getElementById('js-sketch-viewer'));
+ });
+
+ it('does not render error message', () => {
+ expect(document.querySelector('#js-sketch-viewer p')).toBeNull();
+ });
+
+ it('removes the loading icon', () => {
+ expect(document.querySelector('.js-loading-icon')).toBeNull();
+ });
+
+ it('renders preview img', () => {
+ const img = document.querySelector('#js-sketch-viewer img');
+
+ expect(img).not.toBeNull();
+ expect(img.classList.contains('img-fluid')).toBeTruthy();
+ });
+
+ it('renders link to image', () => {
+ const img = document.querySelector('#js-sketch-viewer img');
+ const link = document.querySelector('#js-sketch-viewer a');
+
+ expect(link.href).toBe(img.src);
+ expect(link.target).toBe('_blank');
+ });
+ });
+});
diff --git a/spec/frontend/clusters_list/mock_data.js b/spec/frontend/clusters_list/mock_data.js
index 1812bf9b03f..5398975d81c 100644
--- a/spec/frontend/clusters_list/mock_data.js
+++ b/spec/frontend/clusters_list/mock_data.js
@@ -5,6 +5,7 @@ export default [
size: '3',
clusterType: 'group_type',
status: 'disabled',
+ cpu: '6 (100% free)',
memory: '22.50 (30% free)',
},
{
@@ -13,6 +14,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'unreachable',
+ cpu: '3 (50% free)',
memory: '11 (60% free)',
},
{
@@ -21,6 +23,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'authentication_failure',
+ cpu: '1 (0% free)',
memory: '22 (33% free)',
},
{
@@ -29,6 +32,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'deleting',
+ cpu: '6 (100% free)',
memory: '45 (15% free)',
},
{
@@ -37,6 +41,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'connected',
+ cpu: '6 (100% free)',
memory: '20.12 (35% free)',
},
];
diff --git a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
index 292b8694fbc..14f2a527dfb 100644
--- a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
+++ b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
@@ -7,22 +7,22 @@ import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
describe('ClusterFormDropdown', () => {
- let vm;
+ let wrapper;
const firstItem = { name: 'item 1', value: 1 };
const secondItem = { name: 'item 2', value: 2 };
const items = [firstItem, secondItem, { name: 'item 3', value: 3 }];
beforeEach(() => {
- vm = shallowMount(ClusterFormDropdown);
+ wrapper = shallowMount(ClusterFormDropdown);
});
- afterEach(() => vm.destroy());
+ afterEach(() => wrapper.destroy());
describe('when initial value is provided', () => {
it('sets selectedItem to initial value', () => {
- vm.setProps({ items, value: secondItem.value });
+ wrapper.setProps({ items, value: secondItem.value });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('toggleText')).toEqual(secondItem.name);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(secondItem.name);
});
});
});
@@ -31,28 +31,29 @@ describe('ClusterFormDropdown', () => {
it('displays placeholder text', () => {
const placeholder = 'placeholder';
- vm.setProps({ placeholder });
+ wrapper.setProps({ placeholder });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('toggleText')).toEqual(placeholder);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(placeholder);
});
});
});
describe('when an item is selected', () => {
beforeEach(() => {
- vm.setProps({ items });
+ wrapper.setProps({ items });
- return vm.vm.$nextTick().then(() => {
- vm.findAll('.js-dropdown-item')
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper
+ .findAll('.js-dropdown-item')
.at(1)
.trigger('click');
- return vm.vm.$nextTick();
+ return wrapper.vm.$nextTick();
});
});
it('emits input event with selected item', () => {
- expect(vm.emitted('input')[0]).toEqual([secondItem.value]);
+ expect(wrapper.emitted('input')[0]).toEqual([secondItem.value]);
});
});
@@ -60,37 +61,54 @@ describe('ClusterFormDropdown', () => {
const value = [1];
beforeEach(() => {
- vm.setProps({ items, multiple: true, value });
- return vm.vm
+ wrapper.setProps({ items, multiple: true, value });
+ return wrapper.vm
.$nextTick()
.then(() => {
- vm.findAll('.js-dropdown-item')
+ wrapper
+ .findAll('.js-dropdown-item')
.at(0)
.trigger('click');
- return vm.vm.$nextTick();
+ return wrapper.vm.$nextTick();
})
.then(() => {
- vm.findAll('.js-dropdown-item')
+ wrapper
+ .findAll('.js-dropdown-item')
.at(1)
.trigger('click');
- return vm.vm.$nextTick();
+ return wrapper.vm.$nextTick();
});
});
it('emits input event with an array of selected items', () => {
- expect(vm.emitted('input')[1]).toEqual([[firstItem.value, secondItem.value]]);
+ expect(wrapper.emitted('input')[1]).toEqual([[firstItem.value, secondItem.value]]);
});
});
describe('when multiple items can be selected', () => {
beforeEach(() => {
- vm.setProps({ items, multiple: true, value: firstItem.value });
- return vm.vm.$nextTick();
+ wrapper.setProps({ items, multiple: true, value: firstItem.value });
+ return wrapper.vm.$nextTick();
});
it('displays a checked GlIcon next to the item', () => {
- expect(vm.find(GlIcon).is('.invisible')).toBe(false);
- expect(vm.find(GlIcon).props('name')).toBe('mobile-issue-close');
+ expect(wrapper.find(GlIcon).is('.invisible')).toBe(false);
+ expect(wrapper.find(GlIcon).props('name')).toBe('mobile-issue-close');
+ });
+ });
+
+ describe('when multiple values can be selected and initial value is null', () => {
+ it('emits input event with an array of a single selected item', () => {
+ wrapper.setProps({ items, multiple: true, value: null });
+
+ return wrapper.vm.$nextTick().then(() => {
+ wrapper
+ .findAll('.js-dropdown-item')
+ .at(0)
+ .trigger('click');
+
+ expect(wrapper.emitted('input')[0]).toEqual([[firstItem.value]]);
+ });
});
});
@@ -101,20 +119,20 @@ describe('ClusterFormDropdown', () => {
const currentValue = 1;
const customLabelItems = [{ [labelProperty]: label, value: currentValue }];
- vm.setProps({ labelProperty, items: customLabelItems, value: currentValue });
+ wrapper.setProps({ labelProperty, items: customLabelItems, value: currentValue });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('toggleText')).toEqual(label);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(label);
});
});
});
describe('when loading', () => {
it('dropdown button isLoading', () => {
- vm.setProps({ loading: true });
+ wrapper.setProps({ loading: true });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('isLoading')).toBe(true);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('isLoading')).toBe(true);
});
});
});
@@ -123,20 +141,20 @@ describe('ClusterFormDropdown', () => {
it('uses loading text as toggle button text', () => {
const loadingText = 'loading text';
- vm.setProps({ loading: true, loadingText });
+ wrapper.setProps({ loading: true, loadingText });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('toggleText')).toEqual(loadingText);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(loadingText);
});
});
});
describe('when disabled', () => {
it('dropdown button isDisabled', () => {
- vm.setProps({ disabled: true });
+ wrapper.setProps({ disabled: true });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('isDisabled')).toBe(true);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('isDisabled')).toBe(true);
});
});
});
@@ -145,20 +163,20 @@ describe('ClusterFormDropdown', () => {
it('uses disabled text as toggle button text', () => {
const disabledText = 'disabled text';
- vm.setProps({ disabled: true, disabledText });
+ wrapper.setProps({ disabled: true, disabledText });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).props('toggleText')).toBe(disabledText);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).props('toggleText')).toBe(disabledText);
});
});
});
describe('when has errors', () => {
it('sets border-danger class selector to dropdown toggle', () => {
- vm.setProps({ hasErrors: true });
+ wrapper.setProps({ hasErrors: true });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownButton).classes('border-danger')).toBe(true);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownButton).classes('border-danger')).toBe(true);
});
});
});
@@ -167,10 +185,10 @@ describe('ClusterFormDropdown', () => {
it('displays error message', () => {
const errorMessage = 'error message';
- vm.setProps({ hasErrors: true, errorMessage });
+ wrapper.setProps({ hasErrors: true, errorMessage });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
});
});
});
@@ -179,10 +197,10 @@ describe('ClusterFormDropdown', () => {
it('displays empty text', () => {
const emptyText = 'error message';
- vm.setProps({ items: [], emptyText });
+ wrapper.setProps({ items: [], emptyText });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('.js-empty-text').text()).toEqual(emptyText);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find('.js-empty-text').text()).toEqual(emptyText);
});
});
});
@@ -190,34 +208,36 @@ describe('ClusterFormDropdown', () => {
it('displays search field placeholder', () => {
const searchFieldPlaceholder = 'Placeholder';
- vm.setProps({ searchFieldPlaceholder });
+ wrapper.setProps({ searchFieldPlaceholder });
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(DropdownSearchInput).props('placeholderText')).toEqual(searchFieldPlaceholder);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.find(DropdownSearchInput).props('placeholderText')).toEqual(
+ searchFieldPlaceholder,
+ );
});
});
it('it filters results by search query', () => {
const searchQuery = secondItem.name;
- vm.setProps({ items });
- vm.setData({ searchQuery });
+ wrapper.setProps({ items });
+ wrapper.setData({ searchQuery });
- return vm.vm.$nextTick().then(() => {
- expect(vm.findAll('.js-dropdown-item').length).toEqual(1);
- expect(vm.find('.js-dropdown-item').text()).toEqual(secondItem.name);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.findAll('.js-dropdown-item').length).toEqual(1);
+ expect(wrapper.find('.js-dropdown-item').text()).toEqual(secondItem.name);
});
});
it('focuses dropdown search input when dropdown is displayed', () => {
- const dropdownEl = vm.find('.dropdown').element;
+ const dropdownEl = wrapper.find('.dropdown').element;
- expect(vm.find(DropdownSearchInput).props('focused')).toBe(false);
+ expect(wrapper.find(DropdownSearchInput).props('focused')).toBe(false);
$(dropdownEl).trigger('shown.bs.dropdown');
- return vm.vm.$nextTick(() => {
- expect(vm.find(DropdownSearchInput).props('focused')).toBe(true);
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find(DropdownSearchInput).props('focused')).toBe(true);
});
});
});
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
new file mode 100644
index 00000000000..26542c3d046
--- /dev/null
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -0,0 +1,334 @@
+import Vue from 'vue';
+import { 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';
+
+import { createStore } from '~/logs/stores';
+import { scrollDown } from '~/lib/utils/scroll_utils';
+import {
+ mockEnvName,
+ mockEnvironments,
+ mockPods,
+ mockLogsResult,
+ mockTrace,
+ mockPodName,
+ mockSearch,
+ mockEnvironmentsEndpoint,
+ mockDocumentationPath,
+} from '../mock_data';
+
+jest.mock('~/lib/utils/scroll_utils');
+
+describe('EnvironmentLogs', () => {
+ let EnvironmentLogsComponent;
+ let store;
+ let wrapper;
+ let state;
+
+ const propsData = {
+ environmentName: mockEnvName,
+ environmentsPath: mockEnvironmentsEndpoint,
+ 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');
+ const findPodsDropdown = () => wrapper.find('.js-pods-dropdown');
+ 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 findLogTrace = () => wrapper.find('.js-log-trace');
+
+ const mockSetInitData = () => {
+ state.pods.options = mockPods;
+ state.environments.current = mockEnvName;
+ [state.pods.current] = state.pods.options;
+
+ state.logs.isComplete = false;
+ state.logs.lines = mockLogsResult;
+ };
+
+ const mockShowPodLogs = podName => {
+ state.pods.options = mockPods;
+ [state.pods.current] = podName;
+
+ state.logs.isComplete = false;
+ state.logs.lines = mockLogsResult;
+ };
+
+ const mockFetchEnvs = () => {
+ state.environments.options = mockEnvironments;
+ };
+
+ const initWrapper = () => {
+ wrapper = shallowMount(EnvironmentLogsComponent, {
+ propsData,
+ store,
+ stubs: {
+ LogControlButtons: {
+ name: 'log-control-buttons-stub',
+ template: '<div/>',
+ methods: {
+ update: updateControlBtnsMock,
+ },
+ },
+ },
+ methods: {
+ ...actionMocks,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ state = store.state.environmentLogs;
+ EnvironmentLogsComponent = Vue.extend(EnvironmentLogs);
+ });
+
+ afterEach(() => {
+ actionMocks.setInitData.mockReset();
+ actionMocks.showPodLogs.mockReset();
+ actionMocks.fetchEnvironments.mockReset();
+
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ it('displays UI elements', () => {
+ initWrapper();
+
+ expect(wrapper.isVueInstance()).toBe(true);
+ expect(wrapper.isEmpty()).toBe(false);
+
+ // top bar
+ expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true);
+ expect(findPodsDropdown().is(GlDropdown)).toBe(true);
+ expect(findLogControlButtons().exists()).toBe(true);
+
+ expect(findSearchBar().exists()).toBe(true);
+ expect(findSearchBar().is(GlSearchBoxByClick)).toBe(true);
+ expect(findTimeRangePicker().exists()).toBe(true);
+ expect(findTimeRangePicker().is(DateTimePicker)).toBe(true);
+
+ // log trace
+ expect(findLogTrace().isEmpty()).toBe(false);
+ });
+
+ it('mounted inits data', () => {
+ initWrapper();
+
+ expect(actionMocks.setInitData).toHaveBeenCalledTimes(1);
+ expect(actionMocks.setInitData).toHaveBeenLastCalledWith({
+ timeRange: expect.objectContaining({
+ default: true,
+ }),
+ environmentName: mockEnvName,
+ podName: null,
+ });
+
+ expect(actionMocks.fetchEnvironments).toHaveBeenCalledTimes(1);
+ expect(actionMocks.fetchEnvironments).toHaveBeenLastCalledWith(mockEnvironmentsEndpoint);
+ });
+
+ describe('loading state', () => {
+ beforeEach(() => {
+ state.pods.options = [];
+
+ state.logs = {
+ lines: [],
+ isLoading: true,
+ };
+
+ state.environments = {
+ options: [],
+ isLoading: true,
+ };
+
+ initWrapper();
+ });
+
+ it('displays a disabled environments dropdown', () => {
+ expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true');
+ expect(findEnvironmentsDropdown().findAll(GlDropdownItem).length).toBe(0);
+ });
+
+ it('displays a disabled pods dropdown', () => {
+ expect(findPodsDropdown().attributes('disabled')).toBe('true');
+ expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0);
+ });
+
+ it('displays a disabled search bar', () => {
+ expect(findSearchBar().exists()).toBe(true);
+ expect(findSearchBar().attributes('disabled')).toBe('true');
+ });
+
+ it('displays a disabled time window dropdown', () => {
+ expect(findTimeRangePicker().attributes('disabled')).toBe('true');
+ });
+
+ it('does not update buttons state', () => {
+ expect(updateControlBtnsMock).not.toHaveBeenCalled();
+ });
+
+ it('shows a logs trace', () => {
+ expect(findLogTrace().text()).toBe('');
+ expect(
+ findLogTrace()
+ .find('.js-build-loader-animation')
+ .isVisible(),
+ ).toBe(true);
+ });
+ });
+
+ describe('legacy environment', () => {
+ beforeEach(() => {
+ state.pods.options = [];
+
+ state.logs = {
+ lines: [],
+ isLoading: false,
+ };
+
+ state.environments = {
+ options: mockEnvironments,
+ current: 'staging',
+ isLoading: false,
+ };
+
+ initWrapper();
+ });
+
+ it('displays a disabled time window dropdown', () => {
+ expect(findTimeRangePicker().attributes('disabled')).toBe('true');
+ });
+
+ it('displays a disabled search bar', () => {
+ expect(findSearchBar().attributes('disabled')).toBe('true');
+ });
+
+ it('displays an alert to upgrade to ES', () => {
+ expect(findInfoAlert().exists()).toBe(true);
+ });
+ });
+
+ describe('state with data', () => {
+ beforeEach(() => {
+ actionMocks.setInitData.mockImplementation(mockSetInitData);
+ actionMocks.showPodLogs.mockImplementation(mockShowPodLogs);
+ actionMocks.fetchEnvironments.mockImplementation(mockFetchEnvs);
+
+ initWrapper();
+ });
+
+ afterEach(() => {
+ scrollDown.mockReset();
+ updateControlBtnsMock.mockReset();
+
+ actionMocks.setInitData.mockReset();
+ actionMocks.showPodLogs.mockReset();
+ actionMocks.fetchEnvironments.mockReset();
+ });
+
+ it('displays an enabled search bar', () => {
+ expect(findSearchBar().attributes('disabled')).toBeFalsy();
+
+ // input a query and click `search`
+ findSearchBar().vm.$emit('input', mockSearch);
+ findSearchBar().vm.$emit('submit');
+
+ expect(actionMocks.setSearch).toHaveBeenCalledTimes(1);
+ expect(actionMocks.setSearch).toHaveBeenCalledWith(mockSearch);
+ });
+
+ it('displays an enabled time window dropdown', () => {
+ expect(findTimeRangePicker().attributes('disabled')).toBeFalsy();
+ });
+
+ it('does not display an alert to upgrade to ES', () => {
+ expect(findInfoAlert().exists()).toBe(false);
+ });
+
+ it('populates environments dropdown', () => {
+ const items = findEnvironmentsDropdown().findAll(GlDropdownItem);
+ expect(findEnvironmentsDropdown().props('text')).toBe(mockEnvName);
+ expect(items.length).toBe(mockEnvironments.length);
+ mockEnvironments.forEach((env, i) => {
+ const item = items.at(i);
+ expect(item.text()).toBe(env.name);
+ });
+ });
+
+ it('populates pods dropdown', () => {
+ const items = findPodsDropdown().findAll(GlDropdownItem);
+
+ expect(findPodsDropdown().props('text')).toBe(mockPodName);
+ expect(items.length).toBe(mockPods.length);
+ mockPods.forEach((pod, i) => {
+ const item = items.at(i);
+ expect(item.text()).toBe(pod);
+ });
+ });
+
+ 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('scrolls to bottom when loaded', () => {
+ expect(scrollDown).toHaveBeenCalledTimes(1);
+ });
+
+ describe('when user clicks', () => {
+ it('environment name, trace is refreshed', () => {
+ const items = findEnvironmentsDropdown().findAll(GlDropdownItem);
+ const index = 1; // any env
+
+ expect(actionMocks.showEnvironment).toHaveBeenCalledTimes(0);
+
+ items.at(index).vm.$emit('click');
+
+ expect(actionMocks.showEnvironment).toHaveBeenCalledTimes(1);
+ expect(actionMocks.showEnvironment).toHaveBeenLastCalledWith(mockEnvironments[index].name);
+ });
+
+ it('pod name, trace is refreshed', () => {
+ const items = findPodsDropdown().findAll(GlDropdownItem);
+ const index = 2; // any pod
+
+ expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
+
+ items.at(index).vm.$emit('click');
+
+ expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
+ expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPods[index]);
+ });
+
+ it('refresh button, trace is refreshed', () => {
+ expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
+
+ findLogControlButtons().vm.$emit('refresh');
+
+ expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
+ expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPodName);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
new file mode 100644
index 00000000000..f344e8189c3
--- /dev/null
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -0,0 +1,108 @@
+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;
+
+ const findScrollToTop = () => wrapper.find('.js-scroll-to-top');
+ const findScrollToBottom = () => wrapper.find('.js-scroll-to-bottom');
+ const findRefreshBtn = () => wrapper.find('.js-refresh-log');
+
+ const initWrapper = () => {
+ wrapper = shallowMount(LogControlButtons);
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ it('displays UI elements', () => {
+ initWrapper();
+
+ expect(wrapper.isVueInstance()).toBe(true);
+ expect(wrapper.isEmpty()).toBe(false);
+
+ expect(findScrollToTop().is(GlButton)).toBe(true);
+ expect(findScrollToBottom().is(GlButton)).toBe(true);
+ expect(findRefreshBtn().is(GlButton)).toBe(true);
+ });
+
+ it('emits a `refresh` event on click on `refresh` button', () => {
+ initWrapper();
+
+ // An `undefined` value means no event was emitted
+ expect(wrapper.emitted('refresh')).toBe(undefined);
+
+ findRefreshBtn().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.emitted('refresh')).toHaveLength(1);
+ });
+ });
+
+ 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);
+ });
+
+ it('click on "scroll to bottom" scrolls down', () => {
+ expect(findScrollToBottom().is('[disabled]')).toBe(false);
+
+ findScrollToBottom().vm.$emit('click');
+
+ expect(scrollDown).toHaveBeenCalledTimes(1); // plus one time when trace was loaded
+ });
+ });
+
+ describe('when scrolling actions are disabled', () => {
+ beforeEach(() => {
+ // mock a short page without a scrollbar
+ canScroll.mockReturnValue(false);
+ isScrolledToBottom.mockReturnValue(true);
+ isScrolledToTop.mockReturnValue(true);
+
+ initWrapper();
+ });
+
+ it('buttons are disabled', () => {
+ wrapper.vm.update();
+ return wrapper.vm.$nextTick(() => {
+ expect(findScrollToTop().is('[disabled]')).toBe(true);
+ expect(findScrollToBottom().is('[disabled]')).toBe(true);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/logs/mock_data.js b/spec/frontend/logs/mock_data.js
new file mode 100644
index 00000000000..4c092a84b36
--- /dev/null
+++ b/spec/frontend/logs/mock_data.js
@@ -0,0 +1,85 @@
+export const mockProjectPath = 'root/autodevops-deploy';
+export const mockEnvName = 'production';
+export const mockEnvironmentsEndpoint = `${mockProjectPath}/environments.json`;
+export const mockEnvId = '99';
+export const mockDocumentationPath = '/documentation.md';
+
+const makeMockEnvironment = (id, name, advancedQuerying) => ({
+ id,
+ project_path: mockProjectPath,
+ name,
+ logs_api_path: '/dummy_logs_path.json',
+ enable_advanced_logs_querying: advancedQuerying,
+});
+
+export const mockEnvironment = makeMockEnvironment(mockEnvId, mockEnvName, true);
+export const mockEnvironments = [
+ mockEnvironment,
+ makeMockEnvironment(101, 'staging', false),
+ makeMockEnvironment(102, 'review/a-feature', false),
+];
+
+export const mockPodName = 'production-764c58d697-aaaaa';
+export const mockPods = [
+ mockPodName,
+ 'production-764c58d697-bbbbb',
+ 'production-764c58d697-ccccc',
+ 'production-764c58d697-ddddd',
+];
+
+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: '- -> /' },
+];
+
+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 | - -> /',
+];
+
+export const mockSearch = 'foo +bar';
diff --git a/spec/frontend/logs/stores/actions_spec.js b/spec/frontend/logs/stores/actions_spec.js
new file mode 100644
index 00000000000..6309126159e
--- /dev/null
+++ b/spec/frontend/logs/stores/actions_spec.js
@@ -0,0 +1,324 @@
+import MockAdapter from 'axios-mock-adapter';
+
+import testAction from 'helpers/vuex_action_helper';
+import * as types from '~/logs/stores/mutation_types';
+import { convertToFixedRange } from '~/lib/utils/datetime_range';
+import logsPageState from '~/logs/stores/state';
+import {
+ setInitData,
+ setSearch,
+ showPodLogs,
+ fetchEnvironments,
+ fetchLogs,
+} from '~/logs/stores/actions';
+
+import { defaultTimeRange } from '~/monitoring/constants';
+
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+
+import {
+ mockProjectPath,
+ mockPodName,
+ mockEnvironmentsEndpoint,
+ mockEnvironments,
+ mockPods,
+ mockLogsResult,
+ mockEnvName,
+ mockSearch,
+} from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/datetime_range');
+jest.mock('~/logs/utils');
+
+const mockDefaultRange = {
+ start: '2020-01-10T18:00:00.000Z',
+ end: '2020-01-10T10:00:00.000Z',
+};
+const mockFixedRange = {
+ start: '2020-01-09T18:06:20.000Z',
+ end: '2020-01-09T18:36:20.000Z',
+};
+const mockRollingRange = {
+ duration: 120,
+};
+const mockRollingRangeAsFixed = {
+ start: '2020-01-10T18:00:00.000Z',
+ end: '2020-01-10T17:58:00.000Z',
+};
+
+describe('Logs Store actions', () => {
+ let state;
+ let mock;
+
+ convertToFixedRange.mockImplementation(range => {
+ if (range === defaultTimeRange) {
+ return { ...mockDefaultRange };
+ }
+ if (range === mockFixedRange) {
+ return { ...mockFixedRange };
+ }
+ if (range === mockRollingRange) {
+ return { ...mockRollingRangeAsFixed };
+ }
+ throw new Error('Invalid time range');
+ });
+
+ beforeEach(() => {
+ state = logsPageState();
+ });
+
+ afterEach(() => {
+ flash.mockClear();
+ });
+
+ 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 },
+ ]));
+ });
+
+ describe('setSearch', () => {
+ it('should commit search mutation', () =>
+ testAction(
+ setSearch,
+ mockSearch,
+ state,
+ [{ type: types.SET_SEARCH, payload: mockSearch }],
+ [{ type: 'fetchLogs' }],
+ ));
+ });
+
+ describe('showPodLogs', () => {
+ it('should commit pod name', () =>
+ testAction(
+ showPodLogs,
+ mockPodName,
+ state,
+ [{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName }],
+ [{ type: 'fetchLogs' }],
+ ));
+ });
+
+ describe('fetchEnvironments', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ it('should commit RECEIVE_ENVIRONMENTS_DATA_SUCCESS mutation on correct data', () => {
+ mock.onGet(mockEnvironmentsEndpoint).replyOnce(200, { environments: mockEnvironments });
+ return testAction(
+ fetchEnvironments,
+ mockEnvironmentsEndpoint,
+ state,
+ [
+ { type: types.REQUEST_ENVIRONMENTS_DATA },
+ { type: types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, payload: mockEnvironments },
+ ],
+ [{ type: 'fetchLogs' }],
+ );
+ });
+
+ it('should commit RECEIVE_ENVIRONMENTS_DATA_ERROR on wrong data', () => {
+ mock.onGet(mockEnvironmentsEndpoint).replyOnce(500);
+ return testAction(
+ fetchEnvironments,
+ mockEnvironmentsEndpoint,
+ state,
+ [
+ { type: types.REQUEST_ENVIRONMENTS_DATA },
+ { type: types.RECEIVE_ENVIRONMENTS_DATA_ERROR },
+ ],
+ [],
+ () => {
+ expect(flash).toHaveBeenCalledTimes(1);
+ },
+ );
+ });
+ });
+
+ describe('fetchLogs', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ 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,
+ [
+ { 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 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;
+
+ const endpoint = '/dummy_logs_path.json';
+
+ mock
+ .onGet(endpoint, {
+ params: {
+ 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';
+
+ mock
+ .onGet(endpoint, {
+ params: {
+ pod_name: mockPodName,
+ search: mockSearch,
+ },
+ })
+ .reply(200, {
+ pod_name: mockPodName,
+ pods: mockPods,
+ logs: mockLogsResult,
+ });
+
+ mock.onGet(endpoint).replyOnce(202); // mock reactive cache
+
+ 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 },
+ ],
+ [],
+ () => {
+ // 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', done => {
+ state.environments.options = mockEnvironments;
+ state.environments.current = mockEnvName;
+
+ const endpoint = '/dummy_logs_path.json';
+
+ mock.onGet(endpoint, { params: { ...mockDefaultRange } }).reply(200, {
+ pod_name: mockPodName,
+ pods: mockPods,
+ logs: mockLogsResult,
+ });
+ mock.onGet(endpoint).replyOnce(202); // mock reactive cache
+
+ 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 },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should commit logs and pod errors when backend fails', () => {
+ 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,
+ null,
+ state,
+ [
+ { type: types.REQUEST_PODS_DATA },
+ { type: types.REQUEST_LOGS_DATA },
+ { type: types.RECEIVE_PODS_DATA_ERROR },
+ { type: types.RECEIVE_LOGS_DATA_ERROR },
+ ],
+ [],
+ () => {
+ expect(flash).toHaveBeenCalledTimes(1);
+ },
+ );
+ });
+ });
+});
diff --git a/spec/frontend/logs/stores/getters_spec.js b/spec/frontend/logs/stores/getters_spec.js
new file mode 100644
index 00000000000..fdce575fa97
--- /dev/null
+++ b/spec/frontend/logs/stores/getters_spec.js
@@ -0,0 +1,40 @@
+import * as getters from '~/logs/stores/getters';
+import logsPageState from '~/logs/stores/state';
+
+import { mockLogsResult, mockTrace } from '../mock_data';
+
+describe('Logs Store getters', () => {
+ let state;
+
+ beforeEach(() => {
+ state = logsPageState();
+ });
+
+ describe('trace', () => {
+ describe('when state is initialized', () => {
+ it('returns an empty string', () => {
+ expect(getters.trace(state)).toEqual('');
+ });
+ });
+
+ describe('when state logs are empty', () => {
+ beforeEach(() => {
+ state.logs.lines = [];
+ });
+
+ it('returns an empty string', () => {
+ expect(getters.trace(state)).toEqual('');
+ });
+ });
+
+ describe('when state logs are set', () => {
+ beforeEach(() => {
+ state.logs.lines = mockLogsResult;
+ });
+
+ it('returns an empty string', () => {
+ expect(getters.trace(state)).toEqual(mockTrace.join('\n'));
+ });
+ });
+ });
+});
diff --git a/spec/frontend/logs/stores/mutations_spec.js b/spec/frontend/logs/stores/mutations_spec.js
new file mode 100644
index 00000000000..dcb358c7d5b
--- /dev/null
+++ b/spec/frontend/logs/stores/mutations_spec.js
@@ -0,0 +1,171 @@
+import mutations from '~/logs/stores/mutations';
+import * as types from '~/logs/stores/mutation_types';
+
+import logsPageState from '~/logs/stores/state';
+import {
+ mockEnvName,
+ mockEnvironments,
+ mockPods,
+ mockPodName,
+ mockLogsResult,
+ mockSearch,
+} from '../mock_data';
+
+describe('Logs Store Mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = logsPageState();
+ });
+
+ it('ensures mutation types are correctly named', () => {
+ Object.keys(types).forEach(k => {
+ expect(k).toEqual(types[k]);
+ });
+ });
+
+ describe('SET_PROJECT_ENVIRONMENT', () => {
+ it('sets the environment', () => {
+ mutations[types.SET_PROJECT_ENVIRONMENT](state, mockEnvName);
+ expect(state.environments.current).toEqual(mockEnvName);
+ });
+ });
+
+ describe('SET_SEARCH', () => {
+ it('sets the search', () => {
+ mutations[types.SET_SEARCH](state, mockSearch);
+ expect(state.search).toEqual(mockSearch);
+ });
+ });
+
+ describe('REQUEST_ENVIRONMENTS_DATA', () => {
+ it('inits data', () => {
+ mutations[types.REQUEST_ENVIRONMENTS_DATA](state);
+ expect(state.environments.options).toEqual([]);
+ expect(state.environments.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_ENVIRONMENTS_DATA_SUCCESS', () => {
+ it('receives environments data and stores it as options', () => {
+ expect(state.environments.options).toEqual([]);
+
+ mutations[types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, mockEnvironments);
+
+ expect(state.environments.options).toEqual(mockEnvironments);
+ expect(state.environments.isLoading).toEqual(false);
+ });
+ });
+
+ describe('RECEIVE_ENVIRONMENTS_DATA_ERROR', () => {
+ it('captures an error loading environments', () => {
+ mutations[types.RECEIVE_ENVIRONMENTS_DATA_ERROR](state);
+
+ expect(state.environments).toEqual({
+ options: [],
+ isLoading: false,
+ current: null,
+ });
+ });
+ });
+
+ describe('REQUEST_LOGS_DATA', () => {
+ it('starts loading for logs', () => {
+ mutations[types.REQUEST_LOGS_DATA](state);
+
+ expect(state.logs).toEqual(
+ expect.objectContaining({
+ lines: [],
+ isLoading: true,
+ isComplete: false,
+ }),
+ );
+ });
+ });
+
+ describe('RECEIVE_LOGS_DATA_SUCCESS', () => {
+ it('receives logs lines', () => {
+ mutations[types.RECEIVE_LOGS_DATA_SUCCESS](state, mockLogsResult);
+
+ expect(state.logs).toEqual(
+ expect.objectContaining({
+ lines: mockLogsResult,
+ isLoading: false,
+ isComplete: true,
+ }),
+ );
+ });
+ });
+
+ describe('RECEIVE_LOGS_DATA_ERROR', () => {
+ 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,
+ }),
+ );
+ });
+ });
+
+ describe('SET_CURRENT_POD_NAME', () => {
+ it('set current pod name', () => {
+ mutations[types.SET_CURRENT_POD_NAME](state, mockPodName);
+
+ expect(state.pods.current).toEqual(mockPodName);
+ });
+ });
+
+ describe('SET_TIME_RANGE', () => {
+ it('sets a default range', () => {
+ expect(state.timeRange.current).toEqual(expect.any(Object));
+ });
+
+ it('sets a time range', () => {
+ const mockRange = {
+ start: '2020-01-10T18:00:00.000Z',
+ end: '2020-01-10T10:00:00.000Z',
+ };
+ mutations[types.SET_TIME_RANGE](state, mockRange);
+
+ expect(state.timeRange.current).toEqual(mockRange);
+ });
+ });
+
+ describe('REQUEST_PODS_DATA', () => {
+ it('receives log data error and stops loading', () => {
+ mutations[types.REQUEST_PODS_DATA](state);
+
+ expect(state.pods).toEqual(
+ expect.objectContaining({
+ options: [],
+ }),
+ );
+ });
+ });
+ describe('RECEIVE_PODS_DATA_SUCCESS', () => {
+ it('receives pods data success', () => {
+ mutations[types.RECEIVE_PODS_DATA_SUCCESS](state, mockPods);
+
+ expect(state.pods).toEqual(
+ expect.objectContaining({
+ options: mockPods,
+ }),
+ );
+ });
+ });
+ describe('RECEIVE_PODS_DATA_ERROR', () => {
+ it('receives pods data error', () => {
+ mutations[types.RECEIVE_PODS_DATA_ERROR](state);
+
+ expect(state.pods).toEqual(
+ expect.objectContaining({
+ options: [],
+ }),
+ );
+ });
+ });
+});
diff --git a/spec/frontend/logs/utils_spec.js b/spec/frontend/logs/utils_spec.js
new file mode 100644
index 00000000000..986fe320363
--- /dev/null
+++ b/spec/frontend/logs/utils_spec.js
@@ -0,0 +1,38 @@
+import { getTimeRange } from '~/logs/utils';
+
+describe('logs/utils', () => {
+ describe('getTimeRange', () => {
+ const nowTimestamp = 1577836800000;
+ const nowString = '2020-01-01T00:00:00.000Z';
+
+ beforeEach(() => {
+ jest.spyOn(Date, 'now').mockImplementation(() => nowTimestamp);
+ });
+
+ afterEach(() => {
+ Date.now.mockRestore();
+ });
+
+ it('returns the right values', () => {
+ expect(getTimeRange(0)).toEqual({
+ start: '2020-01-01T00:00:00.000Z',
+ end: nowString,
+ });
+
+ expect(getTimeRange(60 * 30)).toEqual({
+ start: '2019-12-31T23:30:00.000Z',
+ end: nowString,
+ });
+
+ expect(getTimeRange(60 * 60 * 24 * 7 * 1)).toEqual({
+ start: '2019-12-25T00:00:00.000Z',
+ end: nowString,
+ });
+
+ expect(getTimeRange(60 * 60 * 24 * 7 * 4)).toEqual({
+ start: '2019-12-04T00:00:00.000Z',
+ end: nowString,
+ });
+ });
+ });
+});