diff options
Diffstat (limited to 'spec/frontend/jobs/components/log')
-rw-r--r-- | spec/frontend/jobs/components/log/collapsible_section_spec.js | 71 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/duration_badge_spec.js | 26 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/line_header_spec.js | 119 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/line_number_spec.js | 35 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/line_spec.js | 267 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/log_spec.js | 135 | ||||
-rw-r--r-- | spec/frontend/jobs/components/log/mock_data.js | 218 |
7 files changed, 0 insertions, 871 deletions
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js deleted file mode 100644 index 5adedea28a5..00000000000 --- a/spec/frontend/jobs/components/log/collapsible_section_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue'; -import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'; - -describe('Job Log Collapsible Section', () => { - let wrapper; - - const jobLogEndpoint = 'jobs/335'; - - const findCollapsibleLine = () => wrapper.find('.collapsible-line'); - const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg'); - - const createComponent = (props = {}) => { - wrapper = mount(CollapsibleSection, { - propsData: { - ...props, - }, - }); - }; - - describe('with closed section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionClosed, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the closed state', () => { - expect(findCollapsibleLineSvg().attributes('data-testid')).toBe('chevron-lg-right-icon'); - }); - }); - - describe('with opened section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the open state', () => { - expect(findCollapsibleLineSvg().attributes('data-testid')).toBe('chevron-lg-down-icon'); - }); - - it('renders collapsible lines content', () => { - expect(wrapper.findAll('.js-line').length).toEqual(collapsibleSectionOpened.lines.length); - }); - }); - - it('emits onClickCollapsibleLine on click', async () => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - - findCollapsibleLine().trigger('click'); - - await nextTick(); - expect(wrapper.emitted('onClickCollapsibleLine').length).toBe(1); - }); -}); diff --git a/spec/frontend/jobs/components/log/duration_badge_spec.js b/spec/frontend/jobs/components/log/duration_badge_spec.js deleted file mode 100644 index 644d05366a0..00000000000 --- a/spec/frontend/jobs/components/log/duration_badge_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import DurationBadge from '~/jobs/components/log/duration_badge.vue'; - -describe('Job Log Duration Badge', () => { - let wrapper; - - const data = { - duration: '00:30:01', - }; - - const createComponent = (props = {}) => { - wrapper = shallowMount(DurationBadge, { - propsData: { - ...props, - }, - }); - }; - - beforeEach(() => { - createComponent(data); - }); - - it('renders provided duration', () => { - expect(wrapper.text()).toBe(data.duration); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js deleted file mode 100644 index c02d8c22655..00000000000 --- a/spec/frontend/jobs/components/log/line_header_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import DurationBadge from '~/jobs/components/log/duration_badge.vue'; -import LineHeader from '~/jobs/components/log/line_header.vue'; -import LineNumber from '~/jobs/components/log/line_number.vue'; - -describe('Job Log Header Line', () => { - let wrapper; - - const data = { - line: { - content: [ - { - text: 'Running with gitlab-runner 12.1.0 (de7731dd)', - style: 'term-fg-l-green', - }, - ], - lineNumber: 76, - }, - isClosed: true, - path: '/jashkenas/underscore/-/jobs/335', - }; - - const createComponent = (props = {}) => { - wrapper = mount(LineHeader, { - propsData: { - ...props, - }, - }); - }; - - describe('line', () => { - beforeEach(() => { - createComponent(data); - }); - - it('renders the line number component', () => { - expect(wrapper.findComponent(LineNumber).exists()).toBe(true); - }); - - it('renders a span the provided text', () => { - expect(wrapper.find('span').text()).toBe(data.line.content[0].text); - }); - - it('renders the provided style as a class attribute', () => { - expect(wrapper.find('span').classes()).toContain(data.line.content[0].style); - }); - }); - - describe('when isCloses is true', () => { - beforeEach(() => { - createComponent({ ...data, isClosed: true }); - }); - - it('sets icon name to be chevron-lg-right', () => { - expect(wrapper.vm.iconName).toEqual('chevron-lg-right'); - }); - }); - - describe('when isCloses is false', () => { - beforeEach(() => { - createComponent({ ...data, isClosed: false }); - }); - - it('sets icon name to be chevron-lg-down', () => { - expect(wrapper.vm.iconName).toEqual('chevron-lg-down'); - }); - }); - - describe('on click', () => { - beforeEach(() => { - createComponent(data); - }); - - it('emits toggleLine event', async () => { - wrapper.trigger('click'); - - await nextTick(); - expect(wrapper.emitted().toggleLine.length).toBe(1); - }); - }); - - describe('with duration', () => { - beforeEach(() => { - createComponent({ ...data, duration: '00:10' }); - }); - - it('renders the duration badge', () => { - expect(wrapper.findComponent(DurationBadge).exists()).toBe(true); - }); - }); - - describe('line highlighting', () => { - describe('with hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353#L77`); - - createComponent(data); - }); - - it('highlights line', () => { - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - }); - - describe('without hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353`); - - createComponent(data); - }); - - it('does not highlight line', () => { - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_number_spec.js b/spec/frontend/jobs/components/log/line_number_spec.js deleted file mode 100644 index 4130c124a30..00000000000 --- a/spec/frontend/jobs/components/log/line_number_spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import LineNumber from '~/jobs/components/log/line_number.vue'; - -describe('Job Log Line Number', () => { - let wrapper; - - const data = { - lineNumber: 0, - path: '/jashkenas/underscore/-/jobs/335', - }; - - const createComponent = (props = {}) => { - wrapper = shallowMount(LineNumber, { - propsData: { - ...props, - }, - }); - }; - - beforeEach(() => { - createComponent(data); - }); - - it('renders incremented lineNunber by 1', () => { - expect(wrapper.text()).toBe('1'); - }); - - it('renders link with lineNumber as an ID', () => { - expect(wrapper.attributes().id).toBe('L1'); - }); - - it('links to the provided path with line number as anchor', () => { - expect(wrapper.attributes().href).toBe(`${data.path}#L1`); - }); -}); diff --git a/spec/frontend/jobs/components/log/line_spec.js b/spec/frontend/jobs/components/log/line_spec.js deleted file mode 100644 index fad7a03beef..00000000000 --- a/spec/frontend/jobs/components/log/line_spec.js +++ /dev/null @@ -1,267 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Line from '~/jobs/components/log/line.vue'; -import LineNumber from '~/jobs/components/log/line_number.vue'; -import setWindowLocation from 'helpers/set_window_location_helper'; - -const httpUrl = 'http://example.com'; -const httpsUrl = 'https://example.com'; -const queryUrl = 'https://example.com?param=val'; - -const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {}) => ({ - line: { - content: [ - { - text, - style: 'term-fg-l-green', - }, - ], - lineNumber: 0, - }, - path: '/jashkenas/underscore/-/jobs/335', -}); - -describe('Job Log Line', () => { - let wrapper; - let data; - - const createComponent = (props = {}) => { - wrapper = shallowMount(Line, { - propsData: { - ...props, - }, - }); - }; - - const findLine = () => wrapper.find('span'); - const findLink = () => findLine().find('a'); - const findLinks = () => findLine().findAll('a'); - const findLinkAttributeByIndex = (i) => findLinks().at(i).attributes(); - - beforeEach(() => { - data = mockProps(); - createComponent(data); - }); - - it('renders the line number component', () => { - expect(wrapper.findComponent(LineNumber).exists()).toBe(true); - }); - - it('renders a span the provided text', () => { - expect(findLine().text()).toBe(data.line.content[0].text); - }); - - it('renders the provided style as a class attribute', () => { - expect(findLine().classes()).toContain(data.line.content[0].style); - }); - - describe('job urls as links', () => { - it('renders an http link', () => { - createComponent(mockProps({ text: httpUrl })); - - expect(findLink().text()).toBe(httpUrl); - expect(findLink().attributes().href).toBe(httpUrl); - }); - - it('renders an https link', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().text()).toBe(httpsUrl); - expect(findLink().attributes().href).toBe(httpsUrl); - }); - - it('renders a link with rel nofollow and noopener', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().attributes().rel).toBe('nofollow noopener noreferrer'); - }); - - it('renders a link with corresponding styles', () => { - createComponent(mockProps({ text: httpsUrl })); - - expect(findLink().classes()).toEqual(['gl-reset-color!', 'gl-text-decoration-underline']); - }); - - it('renders links with queries, surrounded by questions marks', () => { - createComponent(mockProps({ text: `Did you see my url ${queryUrl}??` })); - - expect(findLine().text()).toBe('Did you see my url https://example.com?param=val??'); - expect(findLinkAttributeByIndex(0).href).toBe(queryUrl); - }); - - it('renders links with queries, surrounded by exclamation marks', () => { - createComponent(mockProps({ text: `No! The ${queryUrl}!?` })); - - expect(findLine().text()).toBe('No! The https://example.com?param=val!?'); - expect(findLinkAttributeByIndex(0).href).toBe(queryUrl); - }); - - it('renders links that have brackets `[]` in their parameters', () => { - const url = `${httpUrl}?label_name[]=frontend`; - - createComponent(mockProps({ text: url })); - - expect(findLine().text()).toBe(url); - expect(findLinks().at(0).text()).toBe(url); - expect(findLinks().at(0).attributes('href')).toBe(url); - }); - - it('renders multiple links surrounded by text', () => { - createComponent( - mockProps({ text: `Well, my HTTP url: ${httpUrl} and my HTTPS url: ${httpsUrl}` }), - ); - expect(findLine().text()).toBe( - 'Well, my HTTP url: http://example.com and my HTTPS url: https://example.com', - ); - - expect(findLinks()).toHaveLength(2); - - expect(findLinkAttributeByIndex(0).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(1).href).toBe(httpsUrl); - }); - - it('renders multiple links surrounded by text, with other symbols', () => { - createComponent( - mockProps({ text: `${httpUrl}, ${httpUrl}: ${httpsUrl}; ${httpsUrl}. ${httpsUrl}...` }), - ); - expect(findLine().text()).toBe( - 'http://example.com, http://example.com: https://example.com; https://example.com. https://example.com...', - ); - - expect(findLinks()).toHaveLength(5); - - expect(findLinkAttributeByIndex(0).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(1).href).toBe(httpUrl); - expect(findLinkAttributeByIndex(2).href).toBe(httpsUrl); - expect(findLinkAttributeByIndex(3).href).toBe(httpsUrl); - expect(findLinkAttributeByIndex(4).href).toBe(httpsUrl); - }); - - it('renders multiple links surrounded by brackets', () => { - createComponent(mockProps({ text: `(${httpUrl}) <${httpUrl}> {${httpsUrl}}` })); - expect(findLine().text()).toBe( - '(http://example.com) <http://example.com> {https://example.com}', - ); - - const links = findLinks(); - - expect(links).toHaveLength(3); - - expect(links.at(0).text()).toBe(httpUrl); - expect(links.at(0).attributes('href')).toBe(httpUrl); - - expect(links.at(1).text()).toBe(httpUrl); - expect(links.at(1).attributes('href')).toBe(httpUrl); - - expect(links.at(2).text()).toBe(httpsUrl); - expect(links.at(2).attributes('href')).toBe(httpsUrl); - }); - - it('renders text with symbols in it', () => { - const text = 'apt-get update < /dev/null > /dev/null'; - createComponent(mockProps({ text })); - - expect(findLine().text()).toBe(text); - }); - - const jshref = 'javascript:doEvil();'; // eslint-disable-line no-script-url - - it.each` - type | text - ${'html link'} | ${'<a href="#">linked</a>'} - ${'html script'} | ${'<script>doEvil();</script>'} - ${'html strong'} | ${'<strong>highlighted</strong>'} - ${'js'} | ${jshref} - ${'file'} | ${'file:///a-file'} - ${'ftp'} | ${'ftp://example.com/file'} - ${'email'} | ${'email@example.com'} - ${'no scheme'} | ${'example.com/page'} - `('does not render a $type link', ({ text }) => { - createComponent(mockProps({ text })); - expect(findLink().exists()).toBe(false); - }); - }); - - describe('job log search', () => { - const mockSearchResults = [ - { - offset: 1533, - content: [{ text: '$ echo "82.71"', style: 'term-fg-l-green term-bold' }], - section: 'step-script', - lineNumber: 20, - }, - { offset: 1560, content: [{ text: '82.71' }], section: 'step-script', lineNumber: 21 }, - ]; - - it('applies highlight class to search result elements', () => { - createComponent({ - line: { - offset: 1560, - content: [{ text: '82.71' }], - section: 'step-script', - lineNumber: 21, - }, - path: '/root/ci-project/-/jobs/1089', - searchResults: mockSearchResults, - }); - - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - - it('does not apply highlight class to search result elements', () => { - createComponent({ - line: { - offset: 1560, - content: [{ text: 'docker' }], - section: 'step-script', - lineNumber: 29, - }, - path: '/root/ci-project/-/jobs/1089', - searchResults: mockSearchResults, - }); - - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - - describe('job log hash highlighting', () => { - describe('with hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353#L77`); - }); - - it('applies highlight class to job log line', () => { - createComponent({ - line: { - offset: 24526, - content: [{ text: 'job log content' }], - section: 'custom-section', - lineNumber: 76, - }, - path: '/root/ci-project/-/jobs/6353', - }); - - expect(wrapper.classes()).toContain('gl-bg-gray-700'); - }); - }); - - describe('without hash', () => { - beforeEach(() => { - setWindowLocation(`http://foo.com/root/ci-project/-/jobs/6353`); - }); - - it('does not apply highlight class to job log line', () => { - createComponent({ - line: { - offset: 24500, - content: [{ text: 'line' }], - section: 'custom-section', - lineNumber: 10, - }, - path: '/root/ci-project/-/jobs/6353', - }); - - expect(wrapper.classes()).not.toContain('gl-bg-gray-700'); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/log_spec.js b/spec/frontend/jobs/components/log/log_spec.js deleted file mode 100644 index 9407b340950..00000000000 --- a/spec/frontend/jobs/components/log/log_spec.js +++ /dev/null @@ -1,135 +0,0 @@ -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import waitForPromises from 'helpers/wait_for_promises'; -import { scrollToElement } from '~/lib/utils/common_utils'; -import Log from '~/jobs/components/log/log.vue'; -import LogLineHeader from '~/jobs/components/log/line_header.vue'; -import { logLinesParser } from '~/jobs/store/utils'; -import { jobLog } from './mock_data'; - -jest.mock('~/lib/utils/common_utils', () => ({ - ...jest.requireActual('~/lib/utils/common_utils'), - scrollToElement: jest.fn(), -})); - -describe('Job Log', () => { - let wrapper; - let actions; - let state; - let store; - let toggleCollapsibleLineMock; - - Vue.use(Vuex); - - const createComponent = () => { - wrapper = mount(Log, { - store, - }); - }; - - beforeEach(() => { - toggleCollapsibleLineMock = jest.fn(); - actions = { - toggleCollapsibleLine: toggleCollapsibleLineMock, - }; - - state = { - jobLog: logLinesParser(jobLog), - jobLogEndpoint: 'jobs/id', - }; - - store = new Vuex.Store({ - actions, - state, - }); - }); - - const findCollapsibleLine = () => wrapper.findComponent(LogLineHeader); - - describe('line numbers', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders a line number for each open line', () => { - expect(wrapper.find('#L1').text()).toBe('1'); - expect(wrapper.find('#L2').text()).toBe('2'); - expect(wrapper.find('#L3').text()).toBe('3'); - }); - - it('links to the provided path and correct line number', () => { - expect(wrapper.find('#L1').attributes('href')).toBe(`${state.jobLogEndpoint}#L1`); - }); - }); - - describe('collapsible sections', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders a clickable header section', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); - }); - - it('renders an icon with the open state', () => { - expect(findCollapsibleLine().find('[data-testid="chevron-lg-down-icon"]').exists()).toBe( - true, - ); - }); - - describe('on click header section', () => { - it('calls toggleCollapsibleLine', () => { - findCollapsibleLine().trigger('click'); - - expect(toggleCollapsibleLineMock).toHaveBeenCalled(); - }); - }); - }); - - describe('anchor scrolling', () => { - afterEach(() => { - window.location.hash = ''; - }); - - describe('when hash is not present', () => { - it('does not scroll to line number', async () => { - createComponent(); - - await waitForPromises(); - - expect(wrapper.find('#L6').exists()).toBe(false); - expect(scrollToElement).not.toHaveBeenCalled(); - }); - }); - - describe('when hash is present', () => { - beforeEach(() => { - window.location.hash = '#L6'; - }); - - it('scrolls to line number', async () => { - createComponent(); - - state.jobLog = logLinesParser(jobLog, [], '#L6'); - await waitForPromises(); - - expect(scrollToElement).toHaveBeenCalledTimes(1); - - state.jobLog = logLinesParser(jobLog, [], '#L7'); - await waitForPromises(); - - expect(scrollToElement).toHaveBeenCalledTimes(1); - }); - - it('line number within collapsed section is visible', () => { - state.jobLog = logLinesParser(jobLog, [], '#L6'); - - createComponent(); - - expect(wrapper.find('#L6').exists()).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/jobs/components/log/mock_data.js b/spec/frontend/jobs/components/log/mock_data.js deleted file mode 100644 index fa51b92a044..00000000000 --- a/spec/frontend/jobs/components/log/mock_data.js +++ /dev/null @@ -1,218 +0,0 @@ -export const jobLog = [ - { - offset: 1000, - content: [{ text: 'Running with gitlab-runner 12.1.0 (de7731dd)' }], - }, - { - offset: 1001, - content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], - }, - { - offset: 1002, - content: [ - { - text: 'Using Docker executor with image dev.gitlab.org3', - }, - ], - section: 'prepare-executor', - section_header: true, - }, - { - offset: 1003, - content: [{ text: 'Starting service postgres:9.6.14 ...', style: 'text-green' }], - section: 'prepare-executor', - }, - { - offset: 1004, - content: [ - { - text: 'Restore cache', - style: 'term-fg-l-cyan term-bold', - }, - ], - section: 'restore-cache', - section_header: true, - section_options: { - collapsed: 'true', - }, - }, - { - offset: 1005, - content: [ - { - text: 'Checking cache for ruby-gems-debian-bullseye-ruby-3.0-16...', - style: 'term-fg-l-green term-bold', - }, - ], - section: 'restore-cache', - }, -]; - -export const utilsMockData = [ - { - offset: 1001, - content: [{ text: ' on docker-auto-scale-com 8a6210b8' }], - }, - { - offset: 1002, - content: [ - { - text: - 'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.28-lfs-2.9-chrome-84-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34', - }, - ], - section: 'prepare-executor', - section_header: true, - }, - { - offset: 1003, - content: [{ text: 'Starting service postgres:9.6.14 ...' }], - section: 'prepare-executor', - }, - { - offset: 1004, - content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }], - section: 'prepare-executor', - }, - { - offset: 1005, - content: [], - section: 'prepare-executor', - section_duration: '10:00', - }, -]; - -export const originalTrace = [ - { - offset: 1, - content: [ - { - text: 'Downloading', - }, - ], - }, -]; - -export const regularIncremental = [ - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - }, -]; - -export const regularIncrementalRepeated = [ - { - offset: 1, - content: [ - { - text: 'log line', - }, - ], - }, -]; - -export const headerTrace = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, -]; - -export const headerTraceIncremental = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleTrace = [ - { - offset: 1, - section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleTraceIncremental = [ - { - offset: 2, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', - }, -]; - -export const collapsibleSectionClosed = { - offset: 5, - section_header: true, - isHeader: true, - isClosed: true, - line: { - content: [{ text: 'foo' }], - section: 'prepare-script', - lineNumber: 1, - }, - section_duration: '00:03', - lines: [ - { - offset: 80, - content: [{ text: 'this is a collapsible nested section' }], - section: 'prepare-script', - lineNumber: 3, - }, - ], -}; - -export const collapsibleSectionOpened = { - offset: 5, - section_header: true, - isHeader: true, - isClosed: false, - line: { - content: [{ text: 'foo' }], - section: 'prepare-script', - lineNumber: 1, - }, - section_duration: '00:03', - lines: [ - { - offset: 80, - content: [{ text: 'this is a collapsible nested section' }], - section: 'prepare-script', - lineNumber: 3, - }, - ], -}; |