diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-11 15:09:37 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-11 15:09:37 +0000 |
commit | a210c43e0aca0311cc1d3d381763b25979ec72dc (patch) | |
tree | 0325d173da7a6e7bd6c2cdf450d0aa1c4e142d0f /spec/frontend | |
parent | c9687bdf58e9d4a9c3942f587bd4841f42e3b5de (diff) | |
download | gitlab-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.js | 92 | ||||
-rw-r--r-- | spec/frontend/clusters_list/mock_data.js | 5 | ||||
-rw-r--r-- | spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js | 142 | ||||
-rw-r--r-- | spec/frontend/logs/components/environment_logs_spec.js | 334 | ||||
-rw-r--r-- | spec/frontend/logs/components/log_control_buttons_spec.js | 108 | ||||
-rw-r--r-- | spec/frontend/logs/mock_data.js | 85 | ||||
-rw-r--r-- | spec/frontend/logs/stores/actions_spec.js | 324 | ||||
-rw-r--r-- | spec/frontend/logs/stores/getters_spec.js | 40 | ||||
-rw-r--r-- | spec/frontend/logs/stores/mutations_spec.js | 171 | ||||
-rw-r--r-- | spec/frontend/logs/utils_spec.js | 38 |
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, + }); + }); + }); +}); |