diff options
Diffstat (limited to 'spec/frontend/ci/job_details/components/log')
4 files changed, 212 insertions, 238 deletions
diff --git a/spec/frontend/ci/job_details/components/log/collapsible_section_spec.js b/spec/frontend/ci/job_details/components/log/collapsible_section_spec.js deleted file mode 100644 index 5abf2a5ce53..00000000000 --- a/spec/frontend/ci/job_details/components/log/collapsible_section_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import CollapsibleSection from '~/ci/job_details/components/log/collapsible_section.vue'; -import LogLine from '~/ci/job_details/components/log/line.vue'; -import LogLineHeader from '~/ci/job_details/components/log/line_header.vue'; -import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'; - -describe('Job Log Collapsible Section', () => { - let wrapper; - - const jobLogEndpoint = 'jobs/335'; - - const findLogLineHeader = () => wrapper.findComponent(LogLineHeader); - const findLogLineHeaderSvg = () => findLogLineHeader().find('svg'); - const findLogLines = () => wrapper.findAllComponents(LogLine); - - const createComponent = (props = {}) => { - wrapper = mount(CollapsibleSection, { - propsData: { - ...props, - }, - }); - }; - - describe('with closed section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionClosed, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findLogLineHeader().text()).toBe('1 foo'); - expect(findLogLineHeader().attributes('role')).toBe('button'); - }); - - it('renders an icon with a closed state', () => { - expect(findLogLineHeaderSvg().attributes('data-testid')).toBe('chevron-lg-right-icon'); - }); - - it('does not render collapsed lines', () => { - expect(findLogLines()).toHaveLength(0); - }); - }); - - describe('with opened section', () => { - beforeEach(() => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - }); - - it('renders clickable header line', () => { - expect(findLogLineHeader().text()).toContain('foo'); - expect(findLogLineHeader().attributes('role')).toBe('button'); - }); - - it('renders an icon with the open state', () => { - expect(findLogLineHeaderSvg().attributes('data-testid')).toBe('chevron-lg-down-icon'); - }); - - it('renders collapsible lines', () => { - expect(findLogLines().at(0).text()).toContain('this is a collapsible nested section'); - expect(findLogLines()).toHaveLength(collapsibleSectionOpened.lines.length); - }); - }); - - it('emits onClickCollapsibleLine on click', async () => { - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - }); - - findLogLineHeader().trigger('click'); - - await nextTick(); - expect(wrapper.emitted('onClickCollapsibleLine').length).toBe(1); - }); - - describe('with search results', () => { - it('passes isHighlighted prop correctly', () => { - const mockSearchResults = [ - { - content: [{ text: 'foo' }], - lineNumber: 1, - offset: 5, - section: 'prepare-script', - section_header: true, - }, - ]; - - createComponent({ - section: collapsibleSectionOpened, - jobLogEndpoint, - searchResults: mockSearchResults, - }); - - expect(findLogLineHeader().props('isHighlighted')).toBe(true); - }); - }); -}); diff --git a/spec/frontend/ci/job_details/components/log/line_header_spec.js b/spec/frontend/ci/job_details/components/log/line_header_spec.js index c75f5fa30d5..0ac33f5aa5a 100644 --- a/spec/frontend/ci/job_details/components/log/line_header_spec.js +++ b/spec/frontend/ci/job_details/components/log/line_header_spec.js @@ -95,12 +95,14 @@ describe('Job Log Header Line', () => { }); describe('with duration', () => { - beforeEach(() => { + it('renders the duration badge', () => { createComponent({ ...defaultProps, duration: '00:10' }); + expect(wrapper.findComponent(DurationBadge).exists()).toBe(true); }); - it('renders the duration badge', () => { - expect(wrapper.findComponent(DurationBadge).exists()).toBe(true); + it('does not render the duration badge with hidden duration', () => { + createComponent({ ...defaultProps, hideDuration: true, duration: '00:10' }); + expect(wrapper.findComponent(DurationBadge).exists()).toBe(false); }); }); diff --git a/spec/frontend/ci/job_details/components/log/log_spec.js b/spec/frontend/ci/job_details/components/log/log_spec.js index 1931d5046dc..de02c7aad6d 100644 --- a/spec/frontend/ci/job_details/components/log/log_spec.js +++ b/spec/frontend/ci/job_details/components/log/log_spec.js @@ -6,9 +6,12 @@ import waitForPromises from 'helpers/wait_for_promises'; import { scrollToElement } from '~/lib/utils/common_utils'; import Log from '~/ci/job_details/components/log/log.vue'; import LogLineHeader from '~/ci/job_details/components/log/line_header.vue'; +import LineNumber from '~/ci/job_details/components/log/line_number.vue'; import { logLinesParser } from '~/ci/job_details/store/utils'; import { mockJobLog, mockJobLogLineCount } from './mock_data'; +const mockPagePath = 'project/-/jobs/99'; + jest.mock('~/lib/utils/common_utils', () => ({ ...jest.requireActual('~/lib/utils/common_utils'), scrollToElement: jest.fn(), @@ -24,7 +27,12 @@ describe('Job Log', () => { Vue.use(Vuex); const createComponent = (props) => { + store = new Vuex.Store({ actions, state }); + wrapper = mount(Log, { + provide: { + pagePath: mockPagePath, + }, propsData: { ...props, }, @@ -36,39 +44,34 @@ describe('Job Log', () => { toggleCollapsibleLineMock = jest.fn(); actions = { toggleCollapsibleLine: toggleCollapsibleLineMock, + setupFullScreenListeners: jest.fn(), }; + const { lines, sections } = logLinesParser(mockJobLog); + state = { - jobLog: logLinesParser(mockJobLog), - jobLogEndpoint: 'jobs/id', + jobLog: lines, + jobLogSections: sections, }; - - store = new Vuex.Store({ - actions, - state, - }); }); - const findCollapsibleLine = () => wrapper.findComponent(LogLineHeader); - const findAllCollapsibleLines = () => wrapper.findAllComponents(LogLineHeader); + const findLineNumbers = () => wrapper.findAllComponents(LineNumber); + const findLineHeader = () => wrapper.findComponent(LogLineHeader); + const findLineHeaders = () => wrapper.findAllComponents(LogLineHeader); describe('line numbers', () => { beforeEach(() => { createComponent(); }); - it.each([...Array(mockJobLogLineCount).keys()])( - 'renders a line number for each line %d', - (index) => { - const lineNumber = wrapper - .findAll('.js-log-line') - .at(index) - .find(`#L${index + 1}`); + it('renders a line number for each line %d with an href', () => { + for (let i = 0; i < mockJobLogLineCount; i += 1) { + const w = findLineNumbers().at(i); - expect(lineNumber.text()).toBe(`${index + 1}`); - expect(lineNumber.attributes('href')).toBe(`${state.jobLogEndpoint}#L${index + 1}`); - }, - ); + expect(w.text()).toBe(`${i + 1}`); + expect(w.attributes('href')).toBe(`${mockPagePath}#L${i + 1}`); + } + }); }); describe('collapsible sections', () => { @@ -77,22 +80,54 @@ describe('Job Log', () => { }); it('renders a clickable header section', () => { - expect(findCollapsibleLine().attributes('role')).toBe('button'); + expect(findLineHeader().attributes('role')).toBe('button'); }); it('renders an icon with the open state', () => { - expect(findCollapsibleLine().find('[data-testid="chevron-lg-down-icon"]').exists()).toBe( - true, - ); + expect(findLineHeader().find('[data-testid="chevron-lg-down-icon"]').exists()).toBe(true); }); describe('on click header section', () => { it('calls toggleCollapsibleLine', () => { - findCollapsibleLine().trigger('click'); + findLineHeader().trigger('click'); expect(toggleCollapsibleLineMock).toHaveBeenCalled(); }); }); + + describe('duration', () => { + it('shows duration', () => { + expect(findLineHeader().props('duration')).toBe('00:00'); + expect(findLineHeader().props('hideDuration')).toBe(false); + }); + + it('hides duration', () => { + state.jobLogSections['resolve-secrets'].hideDuration = true; + createComponent(); + + expect(findLineHeader().props('duration')).toBe('00:00'); + expect(findLineHeader().props('hideDuration')).toBe(true); + }); + }); + + describe('when a section is collapsed', () => { + beforeEach(() => { + state.jobLogSections['prepare-executor'].isClosed = true; + + createComponent(); + }); + + it('hides lines in section', () => { + expect(findLineNumbers().wrappers.map((w) => w.text())).toEqual([ + '1', + '2', + '3', + '4', + // closed section not shown + '7', + ]); + }); + }); }); describe('anchor scrolling', () => { @@ -119,19 +154,19 @@ describe('Job Log', () => { it('scrolls to line number', async () => { createComponent(); - state.jobLog = logLinesParser(mockJobLog, [], '#L6'); + state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines; await waitForPromises(); expect(scrollToElement).toHaveBeenCalledTimes(1); - state.jobLog = logLinesParser(mockJobLog, [], '#L7'); + state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines; await waitForPromises(); expect(scrollToElement).toHaveBeenCalledTimes(1); }); it('line number within collapsed section is visible', () => { - state.jobLog = logLinesParser(mockJobLog, [], '#L6'); + state.jobLog = logLinesParser(mockJobLog, [], '#L6').lines; createComponent(); @@ -150,15 +185,14 @@ describe('Job Log', () => { }, ], section: 'prepare-executor', - section_header: true, lineNumber: 3, }, ]; createComponent({ searchResults: mockSearchResults }); - expect(findAllCollapsibleLines().at(0).props('isHighlighted')).toBe(true); - expect(findAllCollapsibleLines().at(1).props('isHighlighted')).toBe(false); + expect(findLineHeaders().at(0).props('isHighlighted')).toBe(true); + expect(findLineHeaders().at(1).props('isHighlighted')).toBe(false); }); }); }); diff --git a/spec/frontend/ci/job_details/components/log/mock_data.js b/spec/frontend/ci/job_details/components/log/mock_data.js index d9b1354f475..066f783586b 100644 --- a/spec/frontend/ci/job_details/components/log/mock_data.js +++ b/spec/frontend/ci/job_details/components/log/mock_data.js @@ -65,141 +65,182 @@ export const mockContentSection = [ }, ]; -export const mockJobLog = [...mockJobLines, ...mockEmptySection, ...mockContentSection]; - -export const mockJobLogLineCount = 6; // `text` entries in mockJobLog - -export const originalTrace = [ +export const mockJobLogEnd = [ { - offset: 1, - content: [ - { - text: 'Downloading', - }, - ], + offset: 1008, + content: [{ text: 'Job succeeded' }], }, ]; -export const regularIncremental = [ - { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - }, +export const mockJobLog = [ + ...mockJobLines, + ...mockEmptySection, + ...mockContentSection, + ...mockJobLogEnd, ]; -export const regularIncrementalRepeated = [ +export const mockJobLogLineCount = 7; // `text` entries in mockJobLog + +export const mockContentSectionClosed = [ { - offset: 1, + offset: 0, content: [ { - text: 'log line', + text: 'Using Docker executor with image dev.gitlab.org3', }, ], + section: 'mock-closed-section', + section_header: true, + section_options: { collapsed: true }, + }, + { + offset: 1003, + content: [{ text: 'Docker executor with image registry.gitlab.com ...' }], + section: 'mock-closed-section', + }, + { + offset: 1004, + content: [{ text: 'Starting service ...', style: 'term-fg-l-green' }], + section: 'mock-closed-section', + }, + { + offset: 1005, + content: [], + section: 'mock-closed-section', + section_footer: true, + section_duration: '00:09', }, ]; -export const headerTrace = [ +export const mockContentSectionHiddenDuration = [ { - offset: 1, + offset: 0, + content: [{ text: 'Line 1' }], + section: 'mock-hidden-duration-section', section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', + section_options: { hide_duration: 'true' }, + }, + { + offset: 1001, + content: [{ text: 'Line 2' }], + section: 'mock-hidden-duration-section', + }, + { + offset: 1002, + content: [], + section: 'mock-hidden-duration-section', + section_footer: true, + section_duration: '00:09', }, ]; -export const headerTraceIncremental = [ +export const mockContentSubsection = [ { - offset: 1, + offset: 0, + content: [{ text: 'Line 1' }], + section: 'mock-section', section_header: true, - content: [ - { - text: 'updated log line', - }, - ], - section: 'section', }, -]; - -export const collapsibleTrace = [ { - offset: 1, + offset: 1002, + content: [{ text: 'Line 2 - section content' }], + section: 'mock-section', + }, + { + offset: 1003, + content: [{ text: 'Line 3 - sub section header' }], + section: 'sub-section', section_header: true, - content: [ - { - text: 'log line', - }, - ], - section: 'section', }, { - offset: 2, - content: [ - { - text: 'log line', - }, - ], - section: 'section', + offset: 1004, + content: [{ text: 'Line 4 - sub section content' }], + section: 'sub-section', + }, + { + offset: 1005, + content: [{ text: 'Line 5 - sub sub section header with no content' }], + section: 'sub-sub-section', + section_header: true, + }, + { + offset: 1006, + content: [], + section: 'sub-sub-section', + section_footer: true, + section_duration: '00:00', + }, + + { + offset: 1007, + content: [{ text: 'Line 6 - sub section content 2' }], + section: 'sub-section', + }, + { + offset: 1008, + content: [], + section: 'sub-section', + section_footer: true, + section_duration: '00:29', + }, + { + offset: 1009, + content: [{ text: 'Line 7 - section content' }], + section: 'mock-section', + }, + { + offset: 1010, + content: [], + section: 'mock-section', + section_footer: true, + section_duration: '00:59', + }, + { + offset: 1011, + content: [{ text: 'Job succeeded' }], }, ]; -export const collapsibleTraceIncremental = [ +export const mockTruncatedBottomSection = [ + // only the top of a section is obtained, such as when a job gets cancelled { - offset: 2, + offset: 1004, content: [ { - text: 'updated log line', + text: 'Starting job', }, ], - section: 'section', + section: 'mock-section', + section_header: true, + }, + { + offset: 1005, + content: [{ text: 'Job interrupted' }], + section: 'mock-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: 2, - }, - ], -}; - -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: 2, - }, - ], -}; +export const mockTruncatedTopSection = [ + // only the bottom half of a section is obtained, such as when jobs are cut off due to large sizes + { + offset: 1008, + content: [{ text: 'Line N - incomplete section content' }], + section: 'mock-section', + }, + { + offset: 1009, + content: [{ text: 'Line N+1 - incomplete section content' }], + section: 'mock-section', + }, + { + offset: 1010, + content: [], + section: 'mock-section', + section_footer: true, + section_duration: '00:59', + }, + { + offset: 1011, + content: [{ text: 'Job succeeded' }], + }, +]; |