import Vue from 'vue'; 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'; 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'); const module = 'environmentLogs'; jest.mock('lodash/throttle', () => jest.fn(func => { return func; }), ); describe('EnvironmentLogs', () => { let EnvironmentLogsComponent; let store; let dispatch; let wrapper; let state; const propsData = { environmentName: mockEnvName, environmentsPath: mockEnvironmentsEndpoint, clusterApplicationsDocumentationPath: mockDocumentationPath, }; 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 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.lines = []; }; const mockShowPodLogs = () => { state.pods.options = mockPods; [state.pods.current] = mockPods; 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: '
', methods: { update: updateControlBtnsMock, }, props: { scrollDownButtonDisabled: false, }, }, GlInfiniteScroll: { name: 'gl-infinite-scroll', template: `
`, }, GlSprintf, }, }); }; beforeEach(() => { store = createStore(); state = store.state.environmentLogs; EnvironmentLogsComponent = Vue.extend(EnvironmentLogs); jest.spyOn(store, 'dispatch').mockResolvedValue(); dispatch = store.dispatch; }); afterEach(() => { store.dispatch.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(findInfiniteScroll().exists()).toBe(true); expect(findLogTrace().exists()).toBe(true); }); it('mounted inits data', () => { initWrapper(); expect(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, { timeRange: expect.objectContaining({ default: true, }), environmentName: mockEnvName, podName: null, }); expect(dispatch).toHaveBeenCalledWith(`${module}/fetchEnvironments`, mockEnvironmentsEndpoint); }); describe('loading state', () => { beforeEach(() => { state.pods.options = []; state.logs.lines = []; state.logs.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 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( findLogTrace() .find('.js-build-loader-animation') .isVisible(), ).toBe(true); }); }); describe('k8s environment', () => { beforeEach(() => { state.pods.options = []; state.logs.lines = []; state.logs.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(() => { dispatch.mockImplementation(actionName => { if (actionName === `${module}/setInitData`) { mockSetInitData(); } else if (actionName === `${module}/showPodLogs`) { mockShowPodLogs(); } else if (actionName === `${module}/fetchEnvironments`) { mockFetchEnvs(); mockShowPodLogs(); } }); initWrapper(); }); afterEach(() => { scrollDown.mockReset(); updateControlBtnsMock.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(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, expect.any(Object)); expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, 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('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('populates footer', () => { const footer = findLogFooter().text(); expect(footer).toContain(`${mockLogsResult.length} results`); }); describe('when user clicks', () => { it('environment name, trace is refreshed', () => { const items = findEnvironmentsDropdown().findAll(GlDropdownItem); const index = 1; // any env expect(dispatch).not.toHaveBeenCalledWith(`${module}/showEnvironment`, expect.anything()); items.at(index).vm.$emit('click'); 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(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); items.at(index).vm.$emit('click'); expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); }); it('refresh button, trace is refreshed', () => { expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); findLogControlButtons().vm.$emit('refresh'); 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); }); }); }); });