import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; 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, mockEnvironmentsEndpoint, mockDocumentationPath, mockManagedAppsEndpoint, } from '../mock_data'; jest.mock('~/lib/utils/scroll_utils'); const module = 'environmentLogs'; jest.mock('lodash/throttle', () => jest.fn(func => { return func; }), ); describe('EnvironmentLogs', () => { let store; let dispatch; let wrapper; let state; const propsData = { environmentName: mockEnvName, environmentsPath: mockEnvironmentsEndpoint, clusterApplicationsDocumentationPath: mockDocumentationPath, clustersPath: mockManagedAppsEndpoint, }; const updateControlBtnsMock = jest.fn(); const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown'); const findSimpleFilters = () => wrapper.find({ ref: 'log-simple-filters' }); const findAdvancedFilters = () => wrapper.find({ ref: 'log-advanced-filters' }); const findElasticsearchNotice = () => wrapper.find({ ref: 'elasticsearchNotice' }); const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' }); const findInfiniteScroll = () => wrapper.find({ ref: 'infiniteScroll' }); const findLogTrace = () => wrapper.find({ ref: 'logTrace' }); 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(EnvironmentLogs, { 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; 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); expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true); expect(findSimpleFilters().exists()).toBe(true); expect(findLogControlButtons().exists()).toBe(true); 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('does not display an alert to upgrade to ES', () => { expect(findElasticsearchNotice().exists()).toBe(false); }); it('displays a disabled environments dropdown', () => { expect(findEnvironmentsDropdown().attributes('disabled')).toBe('true'); expect(findEnvironmentsDropdown().findAll(GlDropdownItem).length).toBe(0); }); it('does not update buttons state', () => { expect(updateControlBtnsMock).not.toHaveBeenCalled(); }); it('shows an infinite scroll with no content', () => { expect(getInfiniteScrollAttr('fetched-items')).toBe(0); }); it('shows an infinite scroll container with no set max-height ', () => { expect(findInfiniteScroll().attributes('max-list-height')).toBeUndefined(); }); 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 an alert to upgrade to ES', () => { expect(findElasticsearchNotice().exists()).toBe(true); }); it('displays simple filters for kubernetes logs API', () => { expect(findSimpleFilters().exists()).toBe(true); expect(findAdvancedFilters().exists()).toBe(false); }); }); 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('does not display an alert to upgrade to ES', () => { expect(findElasticsearchNotice().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('dropdown has one environment selected', () => { const items = findEnvironmentsDropdown().findAll(GlDropdownItem); mockEnvironments.forEach((env, i) => { const item = items.at(i); if (item.text() !== mockEnvName) { expect(item.find(GlIcon).classes('invisible')).toBe(true); } else { expect(item.find(GlIcon).classes('invisible')).toBe(false); } }); }); it('displays advanced filters for elasticsearch logs API', () => { expect(findSimpleFilters().exists()).toBe(false); expect(findAdvancedFilters().exists()).toBe(true); }); it('shows infinite scroll with content', () => { 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('refresh button, trace is refreshed', () => { expect(dispatch).not.toHaveBeenCalledWith(`${module}/refreshPodLogs`, undefined); findLogControlButtons().vm.$emit('refresh'); expect(dispatch).toHaveBeenCalledWith(`${module}/refreshPodLogs`, undefined); }); }); }); 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); }); }); }); });