Commit 804d5ea7 authored by Jacques Erasmus's avatar Jacques Erasmus Committed by Miguel Rincon

Fix copy as GFM in refactored blob viewer

Fixed the copy as GFM in the refactored viewer
parent 27be56f2
...@@ -35,16 +35,20 @@ export default { ...@@ -35,16 +35,20 @@ export default {
}, },
highlightedContent() { highlightedContent() {
let highlightedContent; let highlightedContent;
let { language } = this;
if (this.hljs) { if (this.hljs) {
if (!this.language) { if (!language) {
highlightedContent = this.hljs.highlightAuto(this.content).value; const hljsHighlightAuto = this.hljs.highlightAuto(this.content);
highlightedContent = hljsHighlightAuto.value;
language = hljsHighlightAuto.language;
} else if (this.languageDefinition) { } else if (this.languageDefinition) {
highlightedContent = this.hljs.highlight(this.content, { language: this.language }).value; highlightedContent = this.hljs.highlight(this.content, { language: this.language }).value;
} }
} }
return wrapLines(highlightedContent); return wrapLines(highlightedContent, language);
}, },
}, },
watch: { watch: {
...@@ -110,7 +114,7 @@ export default { ...@@ -110,7 +114,7 @@ export default {
data-qa-selector="blob_viewer_file_content" data-qa-selector="blob_viewer_file_content"
> >
<line-numbers :lines="lineNumbers" /> <line-numbers :lines="lineNumbers" />
<pre class="code gl-pb-0!"><code v-safe-html="highlightedContent"></code> <pre class="code highlight gl-pb-0!"><code v-safe-html="highlightedContent"></code>
</pre> </pre>
</div> </div>
</template> </template>
export const wrapLines = (content) => { export const wrapLines = (content, language) => {
const isValidLanguage = /^[a-z\d\-_]+$/.test(language); // To prevent the possibility of a vulnerability we only allow languages that contain alphanumeric characters ([a-z\d), dashes (-) or underscores (_).
return ( return (
content && content &&
content content
.split('\n') .split('\n')
.map((line, i) => { .map((line, i) => {
let formattedLine; let formattedLine;
const idAttribute = `id="LC${i + 1}"`; const attributes = `id="LC${i + 1}" lang="${isValidLanguage ? language : ''}"`;
if (line.includes('<span class="hljs') && !line.includes('</span>')) { if (line.includes('<span class="hljs') && !line.includes('</span>')) {
/** /**
...@@ -14,9 +16,9 @@ export const wrapLines = (content) => { ...@@ -14,9 +16,9 @@ export const wrapLines = (content) => {
* example (before): <span class="hljs-code">```bash * example (before): <span class="hljs-code">```bash
* example (after): <span id="LC67" class="hljs-code">```bash * example (after): <span id="LC67" class="hljs-code">```bash
*/ */
formattedLine = line.replace(/(?=class="hljs)/, `${idAttribute} `); formattedLine = line.replace(/(?=class="hljs)/, `${attributes} `);
} else { } else {
formattedLine = `<span ${idAttribute} class="line">${line}</span>`; formattedLine = `<span ${attributes} class="line">${line}</span>`;
} }
return formattedLine; return formattedLine;
......
...@@ -7,10 +7,6 @@ RSpec.describe 'Copy as GFM', :js do ...@@ -7,10 +7,6 @@ RSpec.describe 'Copy as GFM', :js do
include RepoHelpers include RepoHelpers
include ActionView::Helpers::JavaScriptHelper include ActionView::Helpers::JavaScriptHelper
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350454
end
describe 'Copying rendered GFM' do describe 'Copying rendered GFM' do
before do before do
@feat = MarkdownFeature.new @feat = MarkdownFeature.new
...@@ -764,8 +760,8 @@ RSpec.describe 'Copy as GFM', :js do ...@@ -764,8 +760,8 @@ RSpec.describe 'Copy as GFM', :js do
context 'selecting one word of text' do context 'selecting one word of text' do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'.line[id="LC9"] .no', '.line[id="LC10"]',
'`RuntimeError`' '`end`'
) )
end end
end end
...@@ -834,6 +830,7 @@ RSpec.describe 'Copy as GFM', :js do ...@@ -834,6 +830,7 @@ RSpec.describe 'Copy as GFM', :js do
end end
def verify(selector, gfm, target: nil) def verify(selector, gfm, target: nil)
expect(page).to have_selector('.js-syntax-highlight')
html = html_for_selector(selector) html = html_for_selector(selector)
output_gfm = html_to_gfm(html, 'transformCodeSelection', target: target) output_gfm = html_to_gfm(html, 'transformCodeSelection', target: target)
wait_for_requests wait_for_requests
......
...@@ -7,6 +7,7 @@ import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vu ...@@ -7,6 +7,7 @@ import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vu
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants'; import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
import LineNumbers from '~/vue_shared/components/line_numbers.vue'; import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import * as sourceViewerUtils from '~/vue_shared/components/source_viewer/utils';
jest.mock('highlight.js/lib/core'); jest.mock('highlight.js/lib/core');
Vue.use(VueRouter); Vue.use(VueRouter);
...@@ -36,6 +37,7 @@ describe('Source Viewer component', () => { ...@@ -36,6 +37,7 @@ describe('Source Viewer component', () => {
beforeEach(() => { beforeEach(() => {
hljs.highlight.mockImplementation(() => ({ value: highlightedContent })); hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent })); hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
jest.spyOn(sourceViewerUtils, 'wrapLines');
return createComponent(); return createComponent();
}); });
...@@ -73,6 +75,10 @@ describe('Source Viewer component', () => { ...@@ -73,6 +75,10 @@ describe('Source Viewer component', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
it('calls the wrapLines helper method with highlightedContent and mappedLanguage', () => {
expect(sourceViewerUtils.wrapLines).toHaveBeenCalledWith(highlightedContent, mappedLanguage);
});
it('renders Line Numbers', () => { it('renders Line Numbers', () => {
expect(findLineNumbers().props('lines')).toBe(1); expect(findLineNumbers().props('lines')).toBe(1);
}); });
......
...@@ -2,12 +2,25 @@ import { wrapLines } from '~/vue_shared/components/source_viewer/utils'; ...@@ -2,12 +2,25 @@ import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
describe('Wrap lines', () => { describe('Wrap lines', () => {
it.each` it.each`
input | output content | language | output
${'line 1'} | ${'<span id="LC1" class="line">line 1</span>'} ${'line 1'} | ${'javascript'} | ${'<span id="LC1" lang="javascript" class="line">line 1</span>'}
${'line 1\nline 2'} | ${`<span id="LC1" class="line">line 1</span>\n<span id="LC2" class="line">line 2</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>'} | ${`<span id="LC1" class="hljs-code">line 1\n<span id="LC2" class="line">line 2</span></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'} | ${'<span id="LC1" class="hljs-code">```bash'} ${'<span class="hljs-code">```bash'} | ${'bash'} | ${'<span id="LC1" lang="bash" class="hljs-code">```bash'}
`('returns lines wrapped in spans containing line numbers', ({ input, output }) => { ${'<span class="hljs-code">```bash'} | ${'valid-language1'} | ${'<span id="LC1" lang="valid-language1" class="hljs-code">```bash'}
expect(wrapLines(input)).toBe(output); ${'<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',
);
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment