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-10-13 18:09:32 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-13 18:09:32 +0300
commitbd25f1d9c685039381df23e49bc52cdcf4ec1b4a (patch)
tree33b3b16ae2ef653f74828f69742154122ff0ac2d /spec/frontend
parent70ce746bd011b101605e6d84f141d1f0c3175831 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js8
-rw-r--r--spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap60
-rw-r--r--spec/frontend/ide/components/jobs/stage_spec.js9
-rw-r--r--spec/frontend/issues/show/components/edited_spec.js16
-rw-r--r--spec/frontend/lib/dompurify_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/index_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/wrap_child_nodes_spec.js22
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/plugins/wrap_comments_spec.js29
-rw-r--r--spec/frontend/vue_shared/directives/safe_html_spec.js116
9 files changed, 166 insertions, 104 deletions
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
index 700c912029c..6f0d93c466c 100644
--- a/spec/frontend/code_navigation/utils/index_spec.js
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -87,5 +87,13 @@ describe('addInteractionClass', () => {
expect(spans[1].textContent).toBe('Text');
expect(spans[2].textContent).toBe(' ');
});
+
+ it('adds the correct class names to wrapped nodes', () => {
+ setHTMLFixture(
+ '<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"><span class="test"> Text </span></div></div></div>',
+ );
+ addInteractionClass({ ...params, wrapTextNodes: true });
+ expect(findAllSpans()[1].classList.contains('test')).toBe(true);
+ });
});
});
diff --git a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap b/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
deleted file mode 100644
index 45444166a50..00000000000
--- a/spec/frontend/ide/components/jobs/__snapshots__/stage_spec.js.snap
+++ /dev/null
@@ -1,60 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`IDE pipeline stage renders stage details & icon 1`] = `
-<div
- class="ide-stage card gl-mt-3"
->
- <div
- class="card-header"
- >
- <ci-icon-stub
- cssclasses=""
- size="24"
- status="[object Object]"
- />
-
- <strong
- class="gl-ml-3 text-truncate"
- data-container="body"
- >
-
- build
-
- </strong>
-
- <div
- class="gl-mr-3 gl-ml-2"
- >
- <gl-badge-stub
- size="md"
- variant="muted"
- >
- 4
- </gl-badge-stub>
- </div>
-
- <gl-icon-stub
- class="ide-stage-collapse-icon"
- name="chevron-lg-down"
- size="16"
- />
- </div>
-
- <div
- class="card-body p-0"
- >
- <item-stub
- job="[object Object]"
- />
- <item-stub
- job="[object Object]"
- />
- <item-stub
- job="[object Object]"
- />
- <item-stub
- job="[object Object]"
- />
- </div>
-</div>
-`;
diff --git a/spec/frontend/ide/components/jobs/stage_spec.js b/spec/frontend/ide/components/jobs/stage_spec.js
index 1d5e5743a4d..52fbff2f497 100644
--- a/spec/frontend/ide/components/jobs/stage_spec.js
+++ b/spec/frontend/ide/components/jobs/stage_spec.js
@@ -18,8 +18,9 @@ describe('IDE pipeline stage', () => {
},
};
- const findHeader = () => wrapper.findComponent({ ref: 'cardHeader' });
- const findJobList = () => wrapper.findComponent({ ref: 'jobList' });
+ const findHeader = () => wrapper.find('[data-testid="card-header"]');
+ const findJobList = () => wrapper.find('[data-testid="job-list"]');
+ const findStageTitle = () => wrapper.find('[data-testid="stage-title"]');
const createComponent = (props) => {
wrapper = shallowMount(Stage, {
@@ -65,9 +66,9 @@ describe('IDE pipeline stage', () => {
expect(wrapper.emitted().clickViewLog[0][0]).toBe(job);
});
- it('renders stage details & icon', () => {
+ it('renders stage title', () => {
createComponent();
- expect(wrapper.element).toMatchSnapshot();
+ expect(findStageTitle().isVisible()).toBe(true);
});
describe('when collapsed', () => {
diff --git a/spec/frontend/issues/show/components/edited_spec.js b/spec/frontend/issues/show/components/edited_spec.js
index 8a240c38b5f..aa6e0a9dceb 100644
--- a/spec/frontend/issues/show/components/edited_spec.js
+++ b/spec/frontend/issues/show/components/edited_spec.js
@@ -1,7 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import { getTimeago } from '~/lib/utils/datetime_utility';
import Edited from '~/issues/show/components/edited.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+const timeago = getTimeago();
+
describe('Edited component', () => {
let wrapper;
@@ -9,7 +12,8 @@ describe('Edited component', () => {
const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
const formatText = (text) => text.trim().replace(/\s\s+/g, ' ');
- const mountComponent = (propsData) => shallowMount(Edited, { propsData });
+ const mountComponent = (propsData) => mount(Edited, { propsData });
+ const updatedAt = '2017-05-15T12:31:04.428Z';
afterEach(() => {
wrapper.destroy();
@@ -17,12 +21,12 @@ describe('Edited component', () => {
it('renders an edited at+by string', () => {
wrapper = mountComponent({
- updatedAt: '2017-05-15T12:31:04.428Z',
+ updatedAt,
updatedByName: 'Some User',
updatedByPath: '/some_user',
});
- expect(formatText(wrapper.text())).toBe('Edited by Some User');
+ expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)} by Some User`);
expect(findAuthorLink().attributes('href')).toBe('/some_user');
expect(findTimeAgoTooltip().exists()).toBe(true);
});
@@ -40,10 +44,10 @@ describe('Edited component', () => {
it('if no updatedByName and updatedByPath is provided, no user element will be rendered', () => {
wrapper = mountComponent({
- updatedAt: '2017-05-15T12:31:04.428Z',
+ updatedAt,
});
- expect(formatText(wrapper.text())).toBe('Edited');
+ expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)}`);
expect(findAuthorLink().exists()).toBe(false);
expect(findTimeAgoTooltip().exists()).toBe(true);
});
diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js
index 5523cc0606e..412408ce377 100644
--- a/spec/frontend/lib/dompurify_spec.js
+++ b/spec/frontend/lib/dompurify_spec.js
@@ -1,4 +1,4 @@
-import { sanitize } from '~/lib/dompurify';
+import { sanitize, defaultConfig } from '~/lib/dompurify';
// GDK
const rootGon = {
@@ -45,7 +45,7 @@ const invalidProtocolUrls = [
/* eslint-enable no-script-url */
const validProtocolUrls = ['slack://open', 'x-devonthink-item://90909', 'x-devonthink-item:90909'];
-const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method'];
+const forbiddenDataAttrs = defaultConfig.FORBID_ATTR;
const acceptedDataAttrs = ['data-random', 'data-custom'];
describe('~/lib/dompurify', () => {
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 dc9954dcc62..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
@@ -2,10 +2,10 @@ import {
registerPlugins,
HLJS_ON_AFTER_HIGHLIGHT,
} from '~/vue_shared/components/source_viewer/plugins/index';
-import wrapComments from '~/vue_shared/components/source_viewer/plugins/wrap_comments';
+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', () => {
@@ -13,6 +13,6 @@ describe('Highlight.js plugin registration', () => {
it('registers our plugins', () => {
expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapBidiChars });
- expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapComments });
+ expect(hljsMock.addPlugin).toHaveBeenCalledWith({ [HLJS_ON_AFTER_HIGHLIGHT]: wrapChildNodes });
});
});
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/directives/safe_html_spec.js b/spec/frontend/vue_shared/directives/safe_html_spec.js
new file mode 100644
index 00000000000..ba1de8e4596
--- /dev/null
+++ b/spec/frontend/vue_shared/directives/safe_html_spec.js
@@ -0,0 +1,116 @@
+import { shallowMount } from '@vue/test-utils';
+import safeHtml from '~/vue_shared/directives/safe_html';
+import { defaultConfig } from '~/lib/dompurify';
+/* eslint-disable no-script-url */
+const invalidProtocolUrls = [
+ 'javascript:alert(1)',
+ 'jAvascript:alert(1)',
+ 'data:text/html,<script>alert(1);</script>',
+ ' javascript:',
+ 'javascript :',
+];
+/* eslint-enable no-script-url */
+const validProtocolUrls = ['slack://open', 'x-devonthink-item://90909', 'x-devonthink-item:90909'];
+
+describe('safe html directive', () => {
+ let wrapper;
+
+ const createComponent = ({ template, html, config } = {}) => {
+ const defaultTemplate = `<div v-safe-html="rawHtml"></div>`;
+ const defaultHtml = 'hello <script>alert(1)</script>world';
+
+ const component = {
+ directives: {
+ safeHtml,
+ },
+ data() {
+ return {
+ rawHtml: html || defaultHtml,
+ config: config || {},
+ };
+ },
+ template: template || defaultTemplate,
+ };
+
+ wrapper = shallowMount(component);
+ };
+
+ describe('default', () => {
+ it('should remove the script tag', () => {
+ createComponent();
+
+ expect(wrapper.html()).toEqual('<div>hello world</div>');
+ });
+
+ it('should remove javascript hrefs', () => {
+ createComponent({ html: '<a href="javascript:prompt(1)">click here</a>' });
+
+ expect(wrapper.html()).toEqual('<div><a>click here</a></div>');
+ });
+
+ it('should remove any existing children', () => {
+ createComponent({
+ template: `<div v-safe-html="rawHtml">foo <i>bar</i></div>`,
+ });
+
+ expect(wrapper.html()).toEqual('<div>hello world</div>');
+ });
+
+ describe('with non-http links', () => {
+ it.each(validProtocolUrls)('should allow %s', (url) => {
+ createComponent({
+ html: `<a href="${url}">internal link</a>`,
+ });
+ expect(wrapper.html()).toContain(`<a href="${url}">internal link</a>`);
+ });
+
+ it.each(invalidProtocolUrls)('should not allow %s', (url) => {
+ createComponent({
+ html: `<a href="${url}">internal link</a>`,
+ });
+ expect(wrapper.html()).toContain(`<a>internal link</a>`);
+ });
+ });
+
+ describe('handles data attributes correctly', () => {
+ const allowedDataAttrs = ['data-safe', 'data-random'];
+
+ it.each(defaultConfig.FORBID_ATTR)('removes dangerous `%s` attribute', (attr) => {
+ const html = `<a ${attr}="true"></a>`;
+ createComponent({ html });
+
+ expect(wrapper.html()).not.toContain(html);
+ });
+
+ it.each(allowedDataAttrs)('does not remove allowed `%s` attribute', (attr) => {
+ const html = `<a ${attr}="true"></a>`;
+ createComponent({ html });
+
+ expect(wrapper.html()).toContain(html);
+ });
+ });
+ });
+
+ describe('advance config', () => {
+ const template = '<div v-safe-html:[config]="rawHtml"></div>';
+ it('should only allow <b> tags', () => {
+ createComponent({
+ template,
+ html: '<a href="javascript:prompt(1)"><b>click here</b></a>',
+ config: { ALLOWED_TAGS: ['b'] },
+ });
+
+ expect(wrapper.html()).toEqual('<div><b>click here</b></div>');
+ });
+
+ it('should strip all html tags', () => {
+ createComponent({
+ template,
+ html: '<a href="javascript:prompt(1)"><u>click here</u></a>',
+ config: { ALLOWED_TAGS: [] },
+ });
+
+ expect(wrapper.html()).toEqual('<div>click here</div>');
+ });
+ });
+});