Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-14 15:09:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-14 15:09:51 +0300
commit9223573b85bcfdd21953f52e0d2c5cb587e366a1 (patch)
tree7dfd09536b948d560fc442014a95a221327b6567 /spec/frontend/vue_shared
parent1fc72cb8765dab466da8555b70eb744a53a74a80 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/vue_shared')
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_new_spec.js.snap (renamed from spec/frontend/vue_shared/components/source_viewer/components/__snapshots__/chunk_spec.js.snap)0
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js121
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js84
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js93
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js192
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js45
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js173
7 files changed, 354 insertions, 354 deletions
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_new_spec.js.snap
index 26c9a6f8d5a..26c9a6f8d5a 100644
--- 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_new_spec.js.snap
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js
deleted file mode 100644
index 395ba92d4c6..00000000000
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js
+++ /dev/null
@@ -1,121 +0,0 @@
-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_deprecated.vue';
-import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
-import LineHighlighter from '~/blob/line_highlighter';
-
-const lineHighlighter = new LineHighlighter();
-jest.mock('~/blob/line_highlighter', () =>
- jest.fn().mockReturnValue({
- highlightHash: jest.fn(),
- }),
-);
-
-const DEFAULT_PROPS = {
- chunkIndex: 2,
- isHighlighted: false,
- content: '// Line 1 content \n // Line 2 content',
- startingFrom: 140,
- totalLines: 50,
- language: 'javascript',
- blamePath: 'blame/file.js',
-};
-
-const hash = '#L142';
-
-describe('Chunk component', () => {
- let wrapper;
- let idleCallbackSpy;
-
- const createComponent = (props = {}) => {
- wrapper = shallowMountExtended(Chunk, {
- mocks: { $route: { hash } },
- propsData: { ...DEFAULT_PROPS, ...props },
- });
- };
-
- const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
- const findChunkLines = () => wrapper.findAllComponents(ChunkLine);
- const findLineNumbers = () => wrapper.findAllByTestId('line-number');
- const findContent = () => wrapper.findByTestId('content');
-
- beforeEach(() => {
- idleCallbackSpy = jest.spyOn(window, 'requestIdleCallback').mockImplementation((fn) => fn());
- createComponent();
- });
-
- describe('Intersection observer', () => {
- it('renders an Intersection observer component', () => {
- expect(findIntersectionObserver().exists()).toBe(true);
- });
-
- it('emits an appear event when intersection-observer appears', () => {
- findIntersectionObserver().vm.$emit('appear');
-
- expect(wrapper.emitted('appear')).toEqual([[DEFAULT_PROPS.chunkIndex]]);
- });
-
- it('does not emit an appear event is isHighlighted is true', () => {
- createComponent({ isHighlighted: true });
- findIntersectionObserver().vm.$emit('appear');
-
- expect(wrapper.emitted('appear')).toEqual(undefined);
- });
- });
-
- describe('rendering', () => {
- it('does not register window.requestIdleCallback if isFirstChunk prop is true, renders lines immediately', () => {
- jest.clearAllMocks();
- createComponent({ isFirstChunk: true });
-
- expect(window.requestIdleCallback).not.toHaveBeenCalled();
- expect(findContent().exists()).toBe(true);
- });
-
- it('does not render a Chunk Line component if isHighlighted is false', () => {
- expect(findChunkLines().length).toBe(0);
- });
-
- it('does not render simplified line numbers and content if browser is not in idle state', () => {
- idleCallbackSpy.mockRestore();
- createComponent();
-
- expect(findLineNumbers()).toHaveLength(0);
- expect(findContent().exists()).toBe(false);
- });
-
- it('renders simplified line numbers and content if isHighlighted is false', () => {
- expect(findLineNumbers().length).toBe(DEFAULT_PROPS.totalLines);
-
- expect(findLineNumbers().at(0).attributes('id')).toBe(`L${DEFAULT_PROPS.startingFrom + 1}`);
-
- expect(findContent().text()).toBe(DEFAULT_PROPS.content);
- });
-
- it('renders Chunk Line components if isHighlighted is true', () => {
- const splitContent = DEFAULT_PROPS.content.split('\n');
- createComponent({ isHighlighted: true });
-
- expect(findChunkLines().length).toBe(splitContent.length);
-
- expect(findChunkLines().at(0).props()).toMatchObject({
- number: DEFAULT_PROPS.startingFrom + 1,
- content: splitContent[0],
- language: DEFAULT_PROPS.language,
- blamePath: DEFAULT_PROPS.blamePath,
- });
- });
-
- it('does not scroll to route hash if last chunk is not loaded', () => {
- expect(LineHighlighter).not.toHaveBeenCalled();
- });
-
- it('scrolls to route hash if last chunk is loaded', async () => {
- createComponent({ totalChunks: DEFAULT_PROPS.chunkIndex + 1 });
- await nextTick();
- expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
- expect(lineHighlighter.highlightHash).toHaveBeenCalledWith(hash);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js
new file mode 100644
index 00000000000..919abc26e05
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_new_spec.js
@@ -0,0 +1,84 @@
+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_new.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 },
+ });
+ };
+
+ 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();
+ });
+
+ 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({ chunkIndex: 1, ...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()).toHaveLength(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()).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();
+ });
+
+ it('renders highlighted content', () => {
+ expect(findContent().text()).toBe(CHUNK_2.highlightedContent);
+ });
+ });
+ });
+});
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 ff50326917f..9e43aa1d707 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
@@ -2,7 +2,27 @@ 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';
+import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
+import LineHighlighter from '~/blob/line_highlighter';
+
+const lineHighlighter = new LineHighlighter();
+jest.mock('~/blob/line_highlighter', () =>
+ jest.fn().mockReturnValue({
+ highlightHash: jest.fn(),
+ }),
+);
+
+const DEFAULT_PROPS = {
+ chunkIndex: 2,
+ isHighlighted: false,
+ content: '// Line 1 content \n // Line 2 content',
+ startingFrom: 140,
+ totalLines: 50,
+ language: 'javascript',
+ blamePath: 'blame/file.js',
+};
+
+const hash = '#L142';
describe('Chunk component', () => {
let wrapper;
@@ -10,12 +30,14 @@ describe('Chunk component', () => {
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(Chunk, {
- propsData: { ...CHUNK_1, ...props },
+ mocks: { $route: { hash } },
+ propsData: { ...DEFAULT_PROPS, ...props },
});
};
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
- const findLineNumbers = () => wrapper.findAllByTestId('line-numbers');
+ const findChunkLines = () => wrapper.findAllComponents(ChunkLine);
+ const findLineNumbers = () => wrapper.findAllByTestId('line-number');
const findContent = () => wrapper.findByTestId('content');
beforeEach(() => {
@@ -28,57 +50,72 @@ describe('Chunk component', () => {
expect(findIntersectionObserver().exists()).toBe(true);
});
- it('renders highlighted content if appear event is emitted', async () => {
- createComponent({ chunkIndex: 1, isHighlighted: false });
+ it('emits an appear event when intersection-observer appears', () => {
findIntersectionObserver().vm.$emit('appear');
- await nextTick();
+ expect(wrapper.emitted('appear')).toEqual([[DEFAULT_PROPS.chunkIndex]]);
+ });
- expect(findContent().exists()).toBe(true);
+ it('does not emit an appear event is isHighlighted is true', () => {
+ createComponent({ isHighlighted: true });
+ findIntersectionObserver().vm.$emit('appear');
+
+ expect(wrapper.emitted('appear')).toEqual(undefined);
});
});
describe('rendering', () => {
- it('does not register window.requestIdleCallback for the first chunk, renders content immediately', () => {
+ it('does not register window.requestIdleCallback if isFirstChunk prop is true, renders lines immediately', () => {
jest.clearAllMocks();
+ createComponent({ isFirstChunk: true });
expect(window.requestIdleCallback).not.toHaveBeenCalled();
- expect(findContent().text()).toBe(CHUNK_1.highlightedContent);
+ expect(findContent().exists()).toBe(true);
+ });
+
+ it('does not render a Chunk Line component if isHighlighted is false', () => {
+ expect(findChunkLines().length).toBe(0);
});
- it('does not render content if browser is not in idle state', () => {
+ it('does not render simplified line numbers and content if browser is not in idle state', () => {
idleCallbackSpy.mockRestore();
- createComponent({ chunkIndex: 1, ...CHUNK_2 });
+ createComponent();
expect(findLineNumbers()).toHaveLength(0);
expect(findContent().exists()).toBe(false);
});
- describe('isHighlighted is false', () => {
- beforeEach(() => createComponent(CHUNK_2));
+ it('renders simplified line numbers and content if isHighlighted is false', () => {
+ expect(findLineNumbers().length).toBe(DEFAULT_PROPS.totalLines);
- it('does not render line numbers', () => {
- expect(findLineNumbers()).toHaveLength(0);
- });
+ expect(findLineNumbers().at(0).attributes('id')).toBe(`L${DEFAULT_PROPS.startingFrom + 1}`);
- it('renders raw content', () => {
- expect(findContent().text()).toBe(CHUNK_2.rawContent);
- });
+ expect(findContent().text()).toBe(DEFAULT_PROPS.content);
});
- describe('isHighlighted is true', () => {
- beforeEach(() => createComponent({ ...CHUNK_2, isHighlighted: true }));
+ it('renders Chunk Line components if isHighlighted is true', () => {
+ const splitContent = DEFAULT_PROPS.content.split('\n');
+ createComponent({ isHighlighted: true });
- it('renders line numbers', () => {
- expect(findLineNumbers()).toHaveLength(CHUNK_2.totalLines);
+ expect(findChunkLines().length).toBe(splitContent.length);
- // Opted for a snapshot test here since the output is simple and verifies native HTML elements
- expect(findLineNumbers().at(0).element).toMatchSnapshot();
+ expect(findChunkLines().at(0).props()).toMatchObject({
+ number: DEFAULT_PROPS.startingFrom + 1,
+ content: splitContent[0],
+ language: DEFAULT_PROPS.language,
+ blamePath: DEFAULT_PROPS.blamePath,
});
+ });
- it('renders highlighted content', () => {
- expect(findContent().text()).toBe(CHUNK_2.highlightedContent);
- });
+ it('does not scroll to route hash if last chunk is not loaded', () => {
+ expect(LineHighlighter).not.toHaveBeenCalled();
+ });
+
+ it('scrolls to route hash if last chunk is loaded', async () => {
+ createComponent({ totalChunks: DEFAULT_PROPS.chunkIndex + 1 });
+ await nextTick();
+ expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
+ expect(lineHighlighter.highlightHash).toHaveBeenCalledWith(hash);
});
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js
deleted file mode 100644
index e46c30d7c49..00000000000
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js
+++ /dev/null
@@ -1,192 +0,0 @@
-import hljs from 'highlight.js/lib/core';
-import Vue from 'vue';
-import VueRouter from 'vue-router';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer_deprecated.vue';
-import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
-import Chunk from '~/vue_shared/components/source_viewer/components/chunk_deprecated.vue';
-import {
- EVENT_ACTION,
- EVENT_LABEL_VIEWER,
- EVENT_LABEL_FALLBACK,
- ROUGE_TO_HLJS_LANGUAGE_MAP,
- LINES_PER_CHUNK,
- LEGACY_FALLBACKS,
- CODEOWNERS_FILE_NAME,
- CODEOWNERS_LANGUAGE,
-} from '~/vue_shared/components/source_viewer/constants';
-import waitForPromises from 'helpers/wait_for_promises';
-import LineHighlighter from '~/blob/line_highlighter';
-import eventHub from '~/notes/event_hub';
-import Tracking from '~/tracking';
-
-jest.mock('~/blob/line_highlighter');
-jest.mock('highlight.js/lib/core');
-jest.mock('~/vue_shared/components/source_viewer/plugins/index');
-Vue.use(VueRouter);
-const router = new VueRouter();
-
-const generateContent = (content, totalLines = 1, delimiter = '\n') => {
- let generatedContent = '';
- for (let i = 0; i < totalLines; i += 1) {
- generatedContent += `Line: ${i + 1} = ${content}${delimiter}`;
- }
- return generatedContent;
-};
-
-const execImmediately = (callback) => callback();
-
-describe('Source Viewer component', () => {
- let wrapper;
- const language = 'docker';
- const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
- const chunk1 = generateContent('// Some source code 1', 70);
- const chunk2 = generateContent('// Some source code 2', 70);
- const chunk3 = generateContent('// Some source code 3', 70, '\r\n');
- const chunk3Result = generateContent('// Some source code 3', 70, '\n');
- const content = chunk1 + chunk2 + chunk3;
- const path = 'some/path.js';
- const blamePath = 'some/blame/path.js';
- const fileType = 'javascript';
- const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath, fileType };
- const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
-
- const createComponent = async (blob = {}) => {
- wrapper = shallowMountExtended(SourceViewer, {
- router,
- propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
- });
- await waitForPromises();
- };
-
- const findChunks = () => wrapper.findAllComponents(Chunk);
-
- beforeEach(() => {
- hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
- hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
- jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
- jest.spyOn(eventHub, '$emit');
- jest.spyOn(Tracking, 'event');
-
- return createComponent();
- });
-
- describe('event tracking', () => {
- it('fires a tracking event when the component is created', () => {
- const eventData = { label: EVENT_LABEL_VIEWER, property: language };
- expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
- });
-
- it('does not emit an error event when the language is supported', () => {
- expect(wrapper.emitted('error')).toBeUndefined();
- });
-
- it('fires a tracking event and emits an error when the language is not supported', () => {
- const unsupportedLanguage = 'apex';
- const eventData = { label: EVENT_LABEL_FALLBACK, property: unsupportedLanguage };
- createComponent({ language: unsupportedLanguage });
-
- expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
- expect(wrapper.emitted('error')).toHaveLength(1);
- });
- });
-
- describe('legacy fallbacks', () => {
- it.each(LEGACY_FALLBACKS)(
- 'tracks a fallback event and emits an error when viewing %s files',
- (fallbackLanguage) => {
- const eventData = { label: EVENT_LABEL_FALLBACK, property: fallbackLanguage };
- createComponent({ language: fallbackLanguage });
-
- expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
- expect(wrapper.emitted('error')).toHaveLength(1);
- },
- );
- });
-
- describe('highlight.js', () => {
- beforeEach(() => createComponent({ language: mappedLanguage }));
-
- it('registers our plugins for Highlight.js', () => {
- expect(registerPlugins).toHaveBeenCalledWith(hljs, fileType, content);
- });
-
- it('registers the language definition', async () => {
- const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
-
- expect(hljs.registerLanguage).toHaveBeenCalledWith(
- mappedLanguage,
- languageDefinition.default,
- );
- });
-
- it('registers json language definition if fileType is package_json', async () => {
- await createComponent({ language: 'json', fileType: 'package_json' });
- const languageDefinition = await import(`highlight.js/lib/languages/json`);
-
- expect(hljs.registerLanguage).toHaveBeenCalledWith('json', languageDefinition.default);
- });
-
- it('correctly maps languages starting with uppercase', async () => {
- await createComponent({ language: 'Ruby' });
- const languageDefinition = await import(`highlight.js/lib/languages/ruby`);
-
- expect(hljs.registerLanguage).toHaveBeenCalledWith('ruby', languageDefinition.default);
- });
-
- it('registers codeowners language definition if file name is CODEOWNERS', async () => {
- await createComponent({ name: CODEOWNERS_FILE_NAME });
- const languageDefinition = await import(
- '~/vue_shared/components/source_viewer/languages/codeowners'
- );
-
- expect(hljs.registerLanguage).toHaveBeenCalledWith(
- CODEOWNERS_LANGUAGE,
- languageDefinition.default,
- );
- });
-
- it('highlights the first chunk', () => {
- expect(hljs.highlight).toHaveBeenCalledWith(chunk1.trim(), { language: mappedLanguage });
- expect(findChunks().at(0).props('isFirstChunk')).toBe(true);
- });
-
- describe('auto-detects if a language cannot be loaded', () => {
- beforeEach(() => createComponent({ language: 'some_unknown_language' }));
-
- it('highlights the content with auto-detection', () => {
- expect(hljs.highlightAuto).toHaveBeenCalledWith(chunk1.trim());
- });
- });
- });
-
- describe('rendering', () => {
- it.each`
- chunkIndex | chunkContent | totalChunks
- ${0} | ${chunk1} | ${0}
- ${1} | ${chunk2} | ${3}
- ${2} | ${chunk3Result} | ${3}
- `('renders chunk $chunkIndex', ({ chunkIndex, chunkContent, totalChunks }) => {
- const chunk = findChunks().at(chunkIndex);
-
- expect(chunk.props('content')).toContain(chunkContent.trim());
-
- expect(chunk.props()).toMatchObject({
- totalLines: LINES_PER_CHUNK,
- startingFrom: LINES_PER_CHUNK * chunkIndex,
- totalChunks,
- });
- });
-
- it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
- findChunks().at(0).vm.$emit('appear');
- expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
- });
- });
-
- describe('LineHighlighter', () => {
- it('instantiates the lineHighlighter class', () => {
- expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js
new file mode 100644
index 00000000000..715234e56fd
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_new_spec.js
@@ -0,0 +1,45 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer_new.vue';
+import Chunk from '~/vue_shared/components/source_viewer/components/chunk_new.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();
+ });
+
+ 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);
+ });
+ });
+});
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
index 46b582c3668..6b1d65c5a6a 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -1,45 +1,192 @@
+import hljs from 'highlight.js/lib/core';
+import Vue from 'vue';
+import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
+import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index';
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 {
+ EVENT_ACTION,
+ EVENT_LABEL_VIEWER,
+ EVENT_LABEL_FALLBACK,
+ ROUGE_TO_HLJS_LANGUAGE_MAP,
+ LINES_PER_CHUNK,
+ LEGACY_FALLBACKS,
+ CODEOWNERS_FILE_NAME,
+ CODEOWNERS_LANGUAGE,
+} from '~/vue_shared/components/source_viewer/constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import LineHighlighter from '~/blob/line_highlighter';
+import eventHub from '~/notes/event_hub';
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');
+jest.mock('~/blob/line_highlighter');
+jest.mock('highlight.js/lib/core');
+jest.mock('~/vue_shared/components/source_viewer/plugins/index');
+Vue.use(VueRouter);
+const router = new VueRouter();
+
+const generateContent = (content, totalLines = 1, delimiter = '\n') => {
+ let generatedContent = '';
+ for (let i = 0; i < totalLines; i += 1) {
+ generatedContent += `Line: ${i + 1} = ${content}${delimiter}`;
+ }
+ return generatedContent;
+};
+
+const execImmediately = (callback) => callback();
describe('Source Viewer component', () => {
let wrapper;
- const CHUNKS_MOCK = [CHUNK_1, CHUNK_2];
+ const language = 'docker';
+ const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
+ const chunk1 = generateContent('// Some source code 1', 70);
+ const chunk2 = generateContent('// Some source code 2', 70);
+ const chunk3 = generateContent('// Some source code 3', 70, '\r\n');
+ const chunk3Result = generateContent('// Some source code 3', 70, '\n');
+ const content = chunk1 + chunk2 + chunk3;
+ const path = 'some/path.js';
+ const blamePath = 'some/blame/path.js';
+ const fileType = 'javascript';
+ const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath, fileType };
+ const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
- const createComponent = () => {
+ const createComponent = async (blob = {}) => {
wrapper = shallowMountExtended(SourceViewer, {
- propsData: { blob: BLOB_DATA_MOCK, chunks: CHUNKS_MOCK },
+ router,
+ propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
});
+ await waitForPromises();
};
const findChunks = () => wrapper.findAllComponents(Chunk);
beforeEach(() => {
+ hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
+ hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
+ jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
+ jest.spyOn(eventHub, '$emit');
jest.spyOn(Tracking, 'event');
+
return createComponent();
});
describe('event tracking', () => {
it('fires a tracking event when the component is created', () => {
- const eventData = { label: EVENT_LABEL_VIEWER, property: LANGUAGE_MOCK };
+ const eventData = { label: EVENT_LABEL_VIEWER, property: language };
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
+ });
+
+ it('does not emit an error event when the language is supported', () => {
+ expect(wrapper.emitted('error')).toBeUndefined();
+ });
+
+ it('fires a tracking event and emits an error when the language is not supported', () => {
+ const unsupportedLanguage = 'apex';
+ const eventData = { label: EVENT_LABEL_FALLBACK, property: unsupportedLanguage };
+ createComponent({ language: unsupportedLanguage });
+
expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
+
+ describe('legacy fallbacks', () => {
+ it.each(LEGACY_FALLBACKS)(
+ 'tracks a fallback event and emits an error when viewing %s files',
+ (fallbackLanguage) => {
+ const eventData = { label: EVENT_LABEL_FALLBACK, property: fallbackLanguage };
+ createComponent({ language: fallbackLanguage });
+
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ },
+ );
+ });
+
+ describe('highlight.js', () => {
+ beforeEach(() => createComponent({ language: mappedLanguage }));
+
+ it('registers our plugins for Highlight.js', () => {
+ expect(registerPlugins).toHaveBeenCalledWith(hljs, fileType, content);
+ });
+
+ it('registers the language definition', async () => {
+ const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
+
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(
+ mappedLanguage,
+ languageDefinition.default,
+ );
});
- it('adds blob links tracking', () => {
- expect(addBlobLinksTracking).toHaveBeenCalled();
+ it('registers json language definition if fileType is package_json', async () => {
+ await createComponent({ language: 'json', fileType: 'package_json' });
+ const languageDefinition = await import(`highlight.js/lib/languages/json`);
+
+ expect(hljs.registerLanguage).toHaveBeenCalledWith('json', languageDefinition.default);
+ });
+
+ it('correctly maps languages starting with uppercase', async () => {
+ await createComponent({ language: 'Ruby' });
+ const languageDefinition = await import(`highlight.js/lib/languages/ruby`);
+
+ expect(hljs.registerLanguage).toHaveBeenCalledWith('ruby', languageDefinition.default);
+ });
+
+ it('registers codeowners language definition if file name is CODEOWNERS', async () => {
+ await createComponent({ name: CODEOWNERS_FILE_NAME });
+ const languageDefinition = await import(
+ '~/vue_shared/components/source_viewer/languages/codeowners'
+ );
+
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(
+ CODEOWNERS_LANGUAGE,
+ languageDefinition.default,
+ );
+ });
+
+ it('highlights the first chunk', () => {
+ expect(hljs.highlight).toHaveBeenCalledWith(chunk1.trim(), { language: mappedLanguage });
+ expect(findChunks().at(0).props('isFirstChunk')).toBe(true);
+ });
+
+ describe('auto-detects if a language cannot be loaded', () => {
+ beforeEach(() => createComponent({ language: 'some_unknown_language' }));
+
+ it('highlights the content with auto-detection', () => {
+ expect(hljs.highlightAuto).toHaveBeenCalledWith(chunk1.trim());
+ });
});
});
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);
+ it.each`
+ chunkIndex | chunkContent | totalChunks
+ ${0} | ${chunk1} | ${0}
+ ${1} | ${chunk2} | ${3}
+ ${2} | ${chunk3Result} | ${3}
+ `('renders chunk $chunkIndex', ({ chunkIndex, chunkContent, totalChunks }) => {
+ const chunk = findChunks().at(chunkIndex);
+
+ expect(chunk.props('content')).toContain(chunkContent.trim());
+
+ expect(chunk.props()).toMatchObject({
+ totalLines: LINES_PER_CHUNK,
+ startingFrom: LINES_PER_CHUNK * chunkIndex,
+ totalChunks,
+ });
+ });
+
+ it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
+ findChunks().at(0).vm.$emit('appear');
+ expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
+ });
+ });
+
+ describe('LineHighlighter', () => {
+ it('instantiates the lineHighlighter class', () => {
+ expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
});
});
});