diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/source_viewer')
8 files changed, 111 insertions, 60 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 index fd3ff9ce892..f661bd6747a 100644 --- 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 @@ -1,10 +1,5 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue'; -import { - BIDI_CHARS, - BIDI_CHARS_CLASS_LIST, - BIDI_CHAR_TOOLTIP, -} from '~/vue_shared/components/source_viewer/constants'; const DEFAULT_PROPS = { number: 2, @@ -31,7 +26,6 @@ describe('Chunk Line component', () => { const findLineLink = () => wrapper.find('.file-line-num'); const findBlameLink = () => wrapper.find('.file-line-blame'); const findContent = () => wrapper.findByTestId('content'); - const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper'); beforeEach(() => { createComponent(); @@ -40,22 +34,6 @@ describe('Chunk Line component', () => { afterEach(() => wrapper.destroy()); describe('rendering', () => { - it('wraps BiDi characters', () => { - const content = `// some content ${BIDI_CHARS.toString()} with BiDi chars`; - createComponent({ content }); - const wrappedBidiChars = findWrappedBidiChars(); - - expect(wrappedBidiChars.length).toBe(BIDI_CHARS.length); - - wrappedBidiChars.wrappers.forEach((_, i) => { - expect(wrappedBidiChars.at(i).text()).toBe(BIDI_CHARS[i]); - expect(wrappedBidiChars.at(i).attributes()).toMatchObject({ - class: BIDI_CHARS_CLASS_LIST, - title: BIDI_CHAR_TOOLTIP, - }); - }); - }); - it('renders a blame link', () => { expect(findBlameLink().attributes()).toMatchObject({ href: `${DEFAULT_PROPS.blamePath}#L${DEFAULT_PROPS.number}`, diff --git a/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js new file mode 100644 index 00000000000..4a995e2fde1 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/highlight_util_spec.js @@ -0,0 +1,44 @@ +import hljs from 'highlight.js/lib/core'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; +import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index'; +import { highlight } from '~/vue_shared/components/source_viewer/workers/highlight_utils'; + +jest.mock('highlight.js/lib/core', () => ({ + highlight: jest.fn().mockReturnValue({}), + registerLanguage: jest.fn(), +})); + +jest.mock('~/content_editor/services/highlight_js_language_loader', () => ({ + javascript: jest.fn().mockReturnValue({ default: jest.fn() }), +})); + +jest.mock('~/vue_shared/components/source_viewer/plugins/index', () => ({ + registerPlugins: jest.fn(), +})); + +const fileType = 'text'; +const content = 'function test() { return true };'; +const language = 'javascript'; + +describe('Highlight utility', () => { + beforeEach(() => highlight(fileType, content, language)); + + it('loads the language', () => { + expect(languageLoader.javascript).toHaveBeenCalled(); + }); + + it('registers the plugins', () => { + expect(registerPlugins).toHaveBeenCalled(); + }); + + it('registers the language', () => { + expect(hljs.registerLanguage).toHaveBeenCalledWith( + language, + languageLoader[language]().default, + ); + }); + + it('highlights the content', () => { + expect(hljs.highlight).toHaveBeenCalledWith(content, { language }); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js index 83fdc5d669d..57045ca54ae 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js @@ -1,14 +1,18 @@ -import { registerPlugins } from '~/vue_shared/components/source_viewer/plugins/index'; -import { HLJS_ON_AFTER_HIGHLIGHT } from '~/vue_shared/components/source_viewer/constants'; -import wrapComments from '~/vue_shared/components/source_viewer/plugins/wrap_comments'; +import { + registerPlugins, + HLJS_ON_AFTER_HIGHLIGHT, +} from '~/vue_shared/components/source_viewer/plugins/index'; +import wrapChildNodes from '~/vue_shared/components/source_viewer/plugins/wrap_child_nodes'; +import wrapBidiChars from '~/vue_shared/components/source_viewer/plugins/wrap_bidi_chars'; -jest.mock('~/vue_shared/components/source_viewer/plugins/wrap_comments'); +jest.mock('~/vue_shared/components/source_viewer/plugins/wrap_child_nodes'); const hljsMock = { addPlugin: jest.fn() }; describe('Highlight.js plugin registration', () => { beforeEach(() => registerPlugins(hljsMock)); it('registers our plugins', () => { - expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapComments }); + expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapBidiChars }); + expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapChildNodes }); }); }); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js index 8079d5ad99a..e4ce07ec668 100644 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/utils/dependency_linker_util_spec.js @@ -15,7 +15,7 @@ describe('createLink', () => { it('escapes the user-controlled content', () => { const unescapedXSS = '<script>XSS</script>'; const escapedPackageName = '<script>XSS</script>'; - const escapedHref = '&lt;script&gt;XSS&lt;/script&gt;'; + const escapedHref = '<script>XSS</script>'; const href = `http://test.com/${unescapedXSS}`; const innerText = `testing${unescapedXSS}`; const result = `<a href="http://test.com/${escapedHref}" rel="nofollow noreferrer noopener">testing${escapedPackageName}</a>`; diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_bidi_chars_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_bidi_chars_spec.js new file mode 100644 index 00000000000..f40f8b22627 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_bidi_chars_spec.js @@ -0,0 +1,17 @@ +import wrapBidiChars from '~/vue_shared/components/source_viewer/plugins/wrap_bidi_chars'; +import { + BIDI_CHARS, + BIDI_CHARS_CLASS_LIST, + BIDI_CHAR_TOOLTIP, +} from '~/vue_shared/components/source_viewer/constants'; + +describe('Highlight.js plugin for wrapping BiDi characters', () => { + it.each(BIDI_CHARS)('wraps %s BiDi char', (bidiChar) => { + const inputValue = `// some content ${bidiChar} with BiDi chars`; + const outputValue = `// some content <span class="${BIDI_CHARS_CLASS_LIST}" title="${BIDI_CHAR_TOOLTIP}">${bidiChar}</span>`; + const hljsResultMock = { value: inputValue }; + + wrapBidiChars(hljsResultMock); + expect(hljsResultMock.value).toContain(outputValue); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_child_nodes_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_child_nodes_spec.js new file mode 100644 index 00000000000..bc6df1a2565 --- /dev/null +++ b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_child_nodes_spec.js @@ -0,0 +1,22 @@ +import wrapChildNodes from '~/vue_shared/components/source_viewer/plugins/wrap_child_nodes'; + +describe('Highlight.js plugin for wrapping _emitter nodes', () => { + it('mutates the input value by wrapping each node in a span tag', () => { + const hljsResultMock = { + _emitter: { + rootNode: { + children: [ + { kind: 'string', children: ['Text 1'] }, + { kind: 'string', children: ['Text 2', { kind: 'comment', children: ['Text 3'] }] }, + 'Text4\nText5', + ], + }, + }, + }; + + const outputValue = `<span class="hljs-string">Text 1</span><span class="hljs-string"><span class="hljs-string">Text 2</span><span class="hljs-comment">Text 3</span></span><span class="">Text4</span>\n<span class="">Text5</span>`; + + wrapChildNodes(hljsResultMock); + expect(hljsResultMock.value).toBe(outputValue); + }); +}); diff --git a/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js b/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js deleted file mode 100644 index 5fd4182da29..00000000000 --- a/spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import { HLJS_COMMENT_SELECTOR } from '~/vue_shared/components/source_viewer/constants'; -import wrapComments from '~/vue_shared/components/source_viewer/plugins/wrap_comments'; - -describe('Highlight.js plugin for wrapping comments', () => { - it('mutates the input value by wrapping each line in a span tag', () => { - const inputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 \n* Line 2 \n*/</span>`; - const outputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 \n<span class="${HLJS_COMMENT_SELECTOR}">* Line 2 </span>\n<span class="${HLJS_COMMENT_SELECTOR}">*/</span>`; - const hljsResultMock = { value: inputValue }; - - wrapComments(hljsResultMock); - expect(hljsResultMock.value).toBe(outputValue); - }); - - it('does not mutate the input value if the hljs comment selector is not present', () => { - const inputValue = '<span class="hljs-keyword">const</span>'; - const hljsResultMock = { value: inputValue }; - - wrapComments(hljsResultMock); - expect(hljsResultMock.value).toBe(inputValue); - }); - - it('does not mutate the input value if the hljs comment line includes a closing tag', () => { - const inputValue = `<span class="${HLJS_COMMENT_SELECTOR}">/* Line 1 </span> \n* Line 2 \n*/`; - const hljsResultMock = { value: inputValue }; - - wrapComments(hljsResultMock); - expect(hljsResultMock.value).toBe(inputValue); - }); -}); 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 e020d9a557e..6d319b37b02 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 @@ -22,10 +22,10 @@ jest.mock('~/vue_shared/components/source_viewer/plugins/index'); Vue.use(VueRouter); const router = new VueRouter(); -const generateContent = (content, totalLines = 1) => { +const generateContent = (content, totalLines = 1, delimiter = '\n') => { let generatedContent = ''; for (let i = 0; i < totalLines; i += 1) { - generatedContent += `Line: ${i + 1} = ${content}\n`; + generatedContent += `Line: ${i + 1} = ${content}${delimiter}`; } return generatedContent; }; @@ -38,7 +38,9 @@ describe('Source Viewer component', () => { 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 content = chunk1 + chunk2; + 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'; @@ -152,6 +154,19 @@ describe('Source Viewer component', () => { startingFrom: 70, }); }); + + it('renders the third chunk', async () => { + const thirdChunk = findChunks().at(2); + + expect(thirdChunk.props('content')).toContain(chunk3Result.trim()); + + expect(chunk3Result).toEqual(chunk3.replace(/\r?\n/g, '\n')); + + expect(thirdChunk.props()).toMatchObject({ + totalLines: 70, + startingFrom: 140, + }); + }); }); it('emits showBlobInteractionZones on the eventHub when chunk appears', () => { |