Commit 325d8e8b authored by Miguel Rincon's avatar Miguel Rincon

Merge branch '350454-stub-hljs' into 'master'

Fix copy as GFM in the refactored blob viewer

See merge request gitlab-org/gitlab!81396
parents eb847f28 804d5ea7
......@@ -35,16 +35,20 @@ export default {
},
highlightedContent() {
let highlightedContent;
let { language } = this;
if (this.hljs) {
if (!this.language) {
highlightedContent = this.hljs.highlightAuto(this.content).value;
if (!language) {
const hljsHighlightAuto = this.hljs.highlightAuto(this.content);
highlightedContent = hljsHighlightAuto.value;
language = hljsHighlightAuto.language;
} else if (this.languageDefinition) {
highlightedContent = this.hljs.highlight(this.content, { language: this.language }).value;
}
}
return wrapLines(highlightedContent);
return wrapLines(highlightedContent, language);
},
},
watch: {
......@@ -110,7 +114,7 @@ export default {
data-qa-selector="blob_viewer_file_content"
>
<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>
</div>
</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 (
content &&
content
.split('\n')
.map((line, i) => {
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>')) {
/**
......@@ -14,9 +16,9 @@ export const wrapLines = (content) => {
* example (before): <span 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 {
formattedLine = `<span ${idAttribute} class="line">${line}</span>`;
formattedLine = `<span ${attributes} class="line">${line}</span>`;
}
return formattedLine;
......
......@@ -7,10 +7,6 @@ RSpec.describe 'Copy as GFM', :js do
include RepoHelpers
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
before do
@feat = MarkdownFeature.new
......@@ -764,8 +760,8 @@ RSpec.describe 'Copy as GFM', :js do
context 'selecting one word of text' do
it 'copies as inline code' do
verify(
'.line[id="LC9"] .no',
'`RuntimeError`'
'.line[id="LC10"]',
'`end`'
)
end
end
......@@ -834,6 +830,7 @@ RSpec.describe 'Copy as GFM', :js do
end
def verify(selector, gfm, target: nil)
expect(page).to have_selector('.js-syntax-highlight')
html = html_for_selector(selector)
output_gfm = html_to_gfm(html, 'transformCodeSelection', target: target)
wait_for_requests
......
......@@ -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 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';
jest.mock('highlight.js/lib/core');
Vue.use(VueRouter);
......@@ -36,6 +37,7 @@ describe('Source Viewer component', () => {
beforeEach(() => {
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
jest.spyOn(sourceViewerUtils, 'wrapLines');
return createComponent();
});
......@@ -73,6 +75,10 @@ describe('Source Viewer component', () => {
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', () => {
expect(findLineNumbers().props('lines')).toBe(1);
});
......
......@@ -2,12 +2,25 @@ import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
describe('Wrap lines', () => {
it.each`
input | output
${'line 1'} | ${'<span id="LC1" 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>`}
${'<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">```bash'} | ${'<span id="LC1" class="hljs-code">```bash'}
`('returns lines wrapped in spans containing line numbers', ({ input, output }) => {
expect(wrapLines(input)).toBe(output);
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',
);
});
});
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