From b4fd555213a73b6835e40a50edf326c190767b01 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 7 Feb 2023 10:06:31 +0100 Subject: [PATCH 1/3] Add new source viewer component Adds a new source viewer component that can handle highlighted chunks --- .../source_viewer/components/chunk.vue | 133 ++++++++++++++++++ .../source_viewer/source_viewer.vue | 58 ++++++++ .../__snapshots__/chunk_spec.js.snap | 24 ++++ .../source_viewer/components/chunk_spec.js | 87 ++++++++++++ .../components/source_viewer/mock_data.js | 26 ++++ .../source_viewer/source_viewer_spec.js | 47 +++++++ 6 files changed, 375 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue create mode 100644 app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue create mode 100644 spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_spec.js.snap create mode 100644 spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js create mode 100644 spec/frontend/vue_shared/components/source_viewer/mock_data.js create mode 100644 spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue new file mode 100644 index 00000000000000..092e8ba6c1578f --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue @@ -0,0 +1,133 @@ + + diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue new file mode 100644 index 00000000000000..50d286ce9e616e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -0,0 +1,58 @@ + + + diff --git a/spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_spec.js.snap b/spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_spec.js.snap new file mode 100644 index 00000000000000..26c9a6f8d5a837 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_spec.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Chunk component rendering isHighlighted is true renders line numbers 1`] = ` + +`; diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js new file mode 100644 index 00000000000000..b0f114fa2df336 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js @@ -0,0 +1,87 @@ +import { nextTick } from 'vue'; +import { GlIntersectionObserver } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue'; +import { CHUNK_1, CHUNK_2 } from '../mock_data'; + +describe('Chunk component', () => { + let wrapper; + let idleCallbackSpy; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(Chunk, { + propsData: { ...CHUNK_1, ...props }, + provide: { glFeatures: { fileLineBlame: true } }, + }); + }; + + const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver); + const findLineNumbers = () => wrapper.findAllByTestId('line-numbers'); + const findContent = () => wrapper.findByTestId('content'); + + beforeEach(() => { + idleCallbackSpy = jest.spyOn(window, 'requestIdleCallback').mockImplementation((fn) => fn()); + createComponent(); + }); + + afterEach(() => wrapper.destroy()); + + describe('Intersection observer', () => { + it('renders an Intersection observer component', () => { + expect(findIntersectionObserver().exists()).toBe(true); + }); + + it('renders highlighted content if appear event is emitted', async () => { + createComponent({ chunkIndex: 1, isHighlighted: false }); + findIntersectionObserver().vm.$emit('appear'); + + await nextTick(); + + expect(findContent().exists()).toBe(true); + }); + }); + + describe('rendering', () => { + it('does not register window.requestIdleCallback for the first chunk, renders content immediately', () => { + jest.clearAllMocks(); + + expect(window.requestIdleCallback).not.toHaveBeenCalled(); + expect(findContent().text()).toBe(CHUNK_1.highlightedContent); + }); + + it('does not render content if browser is not in idle state', () => { + idleCallbackSpy.mockRestore(); + createComponent(CHUNK_2); + + expect(findLineNumbers()).toHaveLength(0); + expect(findContent().exists()).toBe(false); + }); + + describe('isHighlighted is false', () => { + beforeEach(() => createComponent(CHUNK_2)); + + it('does not render line numbers', () => { + expect(findLineNumbers().length).toBe(0); + }); + + it('renders raw content', () => { + expect(findContent().text()).toBe(CHUNK_2.rawContent); + }); + }); + + describe('isHighlighted is true', () => { + beforeEach(() => createComponent({ ...CHUNK_2, isHighlighted: true })); + + it('renders line numbers', () => { + expect(findLineNumbers().length).toBe(CHUNK_2.totalLines); + + // Opted for a snapshot test here since the output is simple and verifies native HTML elements + expect(findLineNumbers().at(0).element).toMatchSnapshot(); + }); + + it('renders highlighted content', () => { + expect(findContent().text()).toBe(CHUNK_2.highlightedContent); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/mock_data.js new file mode 100644 index 00000000000000..133adf3b4fa481 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/mock_data.js @@ -0,0 +1,26 @@ +const path = 'some/path.js'; +const blamePath = 'some/blame/path.js'; + +export const LANGUAGE_MOCK = 'docker'; + +export const BLOB_DATA_MOCK = { language: LANGUAGE_MOCK, path, blamePath }; + +export const CHUNK_1 = { + chunkIndex: 0, + isHighlighted: true, + rawContent: 'chunk 1 raw', + highlightedContent: 'chunk 1 highlighted', + totalLines: 70, + startingFrom: 0, + blamePath, +}; + +export const CHUNK_2 = { + chunkIndex: 1, + isHighlighted: false, + rawContent: 'chunk 2 raw', + highlightedContent: 'chunk 2 highlighted', + totalLines: 40, + startingFrom: 70, + blamePath, +}; diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js new file mode 100644 index 00000000000000..1c75442b4a8087 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js @@ -0,0 +1,47 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue'; +import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue'; +import { EVENT_ACTION, EVENT_LABEL_VIEWER } from '~/vue_shared/components/source_viewer/constants'; +import Tracking from '~/tracking'; +import addBlobLinksTracking from '~/blob/blob_links_tracking'; +import { BLOB_DATA_MOCK, CHUNK_1, CHUNK_2, LANGUAGE_MOCK } from './mock_data'; + +jest.mock('~/blob/blob_links_tracking'); + +describe('Source Viewer component', () => { + let wrapper; + const CHUNKS_MOCK = [CHUNK_1, CHUNK_2]; + + const createComponent = () => { + wrapper = shallowMountExtended(SourceViewer, { + propsData: { blob: BLOB_DATA_MOCK, chunks: CHUNKS_MOCK }, + }); + }; + + const findChunks = () => wrapper.findAllComponents(Chunk); + + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + return createComponent(); + }); + + afterEach(() => wrapper.destroy()); + + describe('event tracking', () => { + it('fires a tracking event when the component is created', () => { + const eventData = { label: EVENT_LABEL_VIEWER, property: LANGUAGE_MOCK }; + expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData); + }); + + it('adds blob links tracking', () => { + expect(addBlobLinksTracking).toHaveBeenCalled(); + }); + }); + + describe('rendering', () => { + it('renders a Chunk component for each chunk', () => { + expect(findChunks().at(0).props()).toMatchObject(CHUNK_1); + expect(findChunks().at(1).props()).toMatchObject(CHUNK_2); + }); + }); +}); -- GitLab From fbf8c3a6c2bb30e48ada207bebb6a4b3fc3c8695 Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 13 Feb 2023 02:36:33 +0100 Subject: [PATCH 2/3] Update chunk index Update chunk index to the correct one --- .../vue_shared/components/source_viewer/source_viewer.vue | 2 +- .../components/source_viewer/components/chunk_spec.js | 2 +- spec/frontend/vue_shared/components/source_viewer/mock_data.js | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue index 50d286ce9e616e..11708b6f1f61b9 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -46,7 +46,7 @@ export default { { it('does not render content if browser is not in idle state', () => { idleCallbackSpy.mockRestore(); - createComponent(CHUNK_2); + createComponent({ chunkIndex: 1, ...CHUNK_2 }); expect(findLineNumbers()).toHaveLength(0); expect(findContent().exists()).toBe(false); diff --git a/spec/frontend/vue_shared/components/source_viewer/mock_data.js b/spec/frontend/vue_shared/components/source_viewer/mock_data.js index 133adf3b4fa481..f35e9607d5c535 100644 --- a/spec/frontend/vue_shared/components/source_viewer/mock_data.js +++ b/spec/frontend/vue_shared/components/source_viewer/mock_data.js @@ -6,7 +6,6 @@ export const LANGUAGE_MOCK = 'docker'; export const BLOB_DATA_MOCK = { language: LANGUAGE_MOCK, path, blamePath }; export const CHUNK_1 = { - chunkIndex: 0, isHighlighted: true, rawContent: 'chunk 1 raw', highlightedContent: 'chunk 1 highlighted', @@ -16,7 +15,6 @@ export const CHUNK_1 = { }; export const CHUNK_2 = { - chunkIndex: 1, isHighlighted: false, rawContent: 'chunk 2 raw', highlightedContent: 'chunk 2 highlighted', -- GitLab From e7fde70bbd4fbfa529e814293de6553f9be574ab Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 14 Feb 2023 11:52:16 +0100 Subject: [PATCH 3/3] Update spec to use Jest matchers Updated the spec to use toHaveLength instead --- .../components/source_viewer/components/chunk_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js index 52db732b2c6d4b..95ef11d776ae78 100644 --- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js @@ -61,7 +61,7 @@ describe('Chunk component', () => { beforeEach(() => createComponent(CHUNK_2)); it('does not render line numbers', () => { - expect(findLineNumbers().length).toBe(0); + expect(findLineNumbers()).toHaveLength(0); }); it('renders raw content', () => { @@ -73,7 +73,7 @@ describe('Chunk component', () => { beforeEach(() => createComponent({ ...CHUNK_2, isHighlighted: true })); it('renders line numbers', () => { - expect(findLineNumbers().length).toBe(CHUNK_2.totalLines); + expect(findLineNumbers()).toHaveLength(CHUNK_2.totalLines); // Opted for a snapshot test here since the output is simple and verifies native HTML elements expect(findLineNumbers().at(0).element).toMatchSnapshot(); -- GitLab