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>2022-03-21 18:08:37 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-21 18:08:37 +0300
commit84b4743475246e91dc78c3f25f9b335c40be84cd (patch)
treebd65fa4db67a129f7e7f992a27a926a87970a0ed /spec/frontend/vue_shared/components/source_viewer
parent8f2f35ad2e5027a0aedc56dd239ffe38fcc3f09f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/vue_shared/components/source_viewer')
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js47
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js82
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js99
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/utils_spec.js26
4 files changed, 171 insertions, 83 deletions
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
new file mode 100644
index 00000000000..4aa0aea9605
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
@@ -0,0 +1,47 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
+
+const DEFAULT_PROPS = {
+ number: 2,
+ content: '// Line content',
+ language: 'javascript',
+};
+
+describe('Chunk Line component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(ChunkLine, { propsData: { ...DEFAULT_PROPS, ...props } });
+ };
+
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findContent = () => wrapper.findByTestId('content');
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => wrapper.destroy());
+
+ describe('rendering', () => {
+ it('renders a line number', () => {
+ expect(findLink().attributes()).toMatchObject({
+ 'data-line-number': `${DEFAULT_PROPS.number}`,
+ to: `#L${DEFAULT_PROPS.number}`,
+ id: `L${DEFAULT_PROPS.number}`,
+ });
+
+ expect(findLink().text()).toBe(DEFAULT_PROPS.number.toString());
+ });
+
+ it('renders content', () => {
+ expect(findContent().attributes()).toMatchObject({
+ id: `LC${DEFAULT_PROPS.number}`,
+ lang: DEFAULT_PROPS.language,
+ });
+
+ expect(findContent().text()).toBe(DEFAULT_PROPS.content);
+ });
+ });
+});
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 00000000000..42c4f2eacb8
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
@@ -0,0 +1,82 @@
+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 ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
+
+const DEFAULT_PROPS = {
+ chunkIndex: 2,
+ isHighlighted: false,
+ content: '// Line 1 content \n // Line 2 content',
+ startingFrom: 140,
+ totalLines: 50,
+ language: 'javascript',
+};
+
+describe('Chunk component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(Chunk, { 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(() => {
+ createComponent();
+ });
+
+ afterEach(() => wrapper.destroy());
+
+ 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 render a Chunk Line component if isHighlighted is false', () => {
+ expect(findChunkLines().length).toBe(0);
+ });
+
+ it('renders simplified line numbers and content if isHighlighted is false', () => {
+ expect(findLineNumbers().length).toBe(DEFAULT_PROPS.totalLines);
+
+ expect(findLineNumbers().at(0).attributes()).toMatchObject({
+ 'data-line-number': `${DEFAULT_PROPS.startingFrom + 1}`,
+ href: `#L${DEFAULT_PROPS.startingFrom + 1}`,
+ id: `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,
+ });
+ });
+ });
+});
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 ab579945e22..5b787993492 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,23 +1,35 @@
import hljs from 'highlight.js/lib/core';
-import { GlLoadingIcon } from '@gitlab/ui';
-import Vue, { nextTick } from 'vue';
+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 Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
-import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import waitForPromises from 'helpers/wait_for_promises';
-import * as sourceViewerUtils from '~/vue_shared/components/source_viewer/utils';
+import LineHighlighter from '~/blob/line_highlighter';
+jest.mock('~/blob/line_highlighter');
jest.mock('highlight.js/lib/core');
Vue.use(VueRouter);
const router = new VueRouter();
+const generateContent = (content, totalLines = 1) => {
+ let generatedContent = '';
+ for (let i = 0; i < totalLines; i += 1) {
+ generatedContent += `Line: ${i + 1} = ${content}\n`;
+ }
+ return generatedContent;
+};
+
+const execImmediately = (callback) => callback();
+
describe('Source Viewer component', () => {
let wrapper;
const language = 'docker';
const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
- const content = `// Some source code`;
+ const chunk1 = generateContent('// Some source code 1', 70);
+ const chunk2 = generateContent('// Some source code 2', 70);
+ const content = chunk1 + chunk2;
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
@@ -29,15 +41,12 @@ describe('Source Viewer component', () => {
await waitForPromises();
};
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findLineNumbers = () => wrapper.findComponent(LineNumbers);
- const findHighlightedContent = () => wrapper.findByTestId('test-highlighted');
- const findFirstLine = () => wrapper.find('#LC1');
+ const findChunks = () => wrapper.findAllComponents(Chunk);
beforeEach(() => {
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
- jest.spyOn(sourceViewerUtils, 'wrapLines');
+ jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
return createComponent();
});
@@ -45,6 +54,8 @@ describe('Source Viewer component', () => {
afterEach(() => wrapper.destroy());
describe('highlight.js', () => {
+ beforeEach(() => createComponent({ language: mappedLanguage }));
+
it('registers the language definition', async () => {
const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
@@ -54,72 +65,46 @@ describe('Source Viewer component', () => {
);
});
- it('highlights the content', () => {
- expect(hljs.highlight).toHaveBeenCalledWith(content, { language: mappedLanguage });
+ it('highlights the first chunk', () => {
+ expect(hljs.highlight).toHaveBeenCalledWith(chunk1.trim(), { language: mappedLanguage });
});
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(content);
+ expect(hljs.highlightAuto).toHaveBeenCalledWith(chunk1.trim());
});
});
});
describe('rendering', () => {
- it('renders a loading icon if no highlighted content is available yet', async () => {
- hljs.highlight.mockImplementation(() => ({ value: null }));
- await createComponent();
-
- expect(findLoadingIcon().exists()).toBe(true);
- });
+ it('renders the first chunk', async () => {
+ const firstChunk = findChunks().at(0);
- it('calls the wrapLines helper method with highlightedContent and mappedLanguage', () => {
- expect(sourceViewerUtils.wrapLines).toHaveBeenCalledWith(highlightedContent, mappedLanguage);
- });
-
- it('renders Line Numbers', () => {
- expect(findLineNumbers().props('lines')).toBe(1);
- });
+ expect(firstChunk.props('content')).toContain(chunk1);
- it('renders the highlighted content', () => {
- expect(findHighlightedContent().exists()).toBe(true);
- });
- });
-
- describe('selecting a line', () => {
- let firstLine;
- let firstLineElement;
-
- beforeEach(() => {
- firstLine = findFirstLine();
- firstLineElement = firstLine.element;
-
- jest.spyOn(firstLineElement, 'scrollIntoView');
- jest.spyOn(firstLineElement.classList, 'add');
- jest.spyOn(firstLineElement.classList, 'remove');
+ expect(firstChunk.props()).toMatchObject({
+ totalLines: 70,
+ startingFrom: 0,
+ });
});
- it('adds the highlight (hll) class', async () => {
- wrapper.vm.$router.push('#LC1');
- await nextTick();
+ it('renders the second chunk', async () => {
+ const secondChunk = findChunks().at(1);
- expect(firstLineElement.classList.add).toHaveBeenCalledWith('hll');
- });
+ expect(secondChunk.props('content')).toContain(chunk2.trim());
- it('removes the highlight (hll) class from a previously highlighted line', async () => {
- wrapper.vm.$router.push('#LC2');
- await nextTick();
-
- expect(firstLineElement.classList.remove).toHaveBeenCalledWith('hll');
+ expect(secondChunk.props()).toMatchObject({
+ totalLines: 70,
+ startingFrom: 70,
+ });
});
+ });
- it('scrolls the line into view', () => {
- expect(firstLineElement.scrollIntoView).toHaveBeenCalledWith({
- behavior: 'smooth',
- block: 'center',
- });
+ describe('LineHighlighter', () => {
+ it('instantiates the lineHighlighter class', async () => {
+ expect(LineHighlighter).toHaveBeenCalledWith({ scrollBehavior: 'auto' });
});
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/utils_spec.js b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
deleted file mode 100644
index 0631e7efd54..00000000000
--- a/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
-
-describe('Wrap lines', () => {
- it.each`
- content | language | output
- ${'line 1'} | ${'javascript'} | ${'<span id="LC1" lang="javascript" class="line">line 1</span>'}
- ${'line 1\nline 2'} | ${'html'} | ${`<span id="LC1" lang="html" class="line">line 1</span>\n<span id="LC2" lang="html" class="line">line 2</span>`}
- ${'<span class="hljs-code">line 1\nline 2</span>'} | ${'html'} | ${`<span id="LC1" lang="html" class="hljs-code">line 1\n<span id="LC2" lang="html" class="line">line 2</span></span>`}
- ${'<span class="hljs-code">```bash'} | ${'bash'} | ${'<span id="LC1" lang="bash" class="hljs-code">```bash'}
- ${'<span class="hljs-code">```bash'} | ${'valid-language1'} | ${'<span id="LC1" lang="valid-language1" class="hljs-code">```bash'}
- ${'<span class="hljs-code">```bash'} | ${'valid_language2'} | ${'<span id="LC1" lang="valid_language2" class="hljs-code">```bash'}
- `('returns lines wrapped in spans containing line numbers', ({ content, language, output }) => {
- expect(wrapLines(content, language)).toBe(output);
- });
-
- it.each`
- language
- ${'invalidLanguage>'}
- ${'"invalidLanguage"'}
- ${'<invalidLanguage'}
- `('returns lines safely without XSS language is not valid', ({ language }) => {
- expect(wrapLines('<span class="hljs-code">```bash', language)).toBe(
- '<span id="LC1" lang="" class="hljs-code">```bash',
- );
- });
-});