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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-02 09:09:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-02 09:09:00 +0300
commit03c509e17bfa71a124a69b6cf4897414d3bd1cb5 (patch)
tree1301bca7b00224dd4f567d7e43344982a135c14f /spec
parent455e6650ee300263e78a8142d247f538c59737a6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/ide_spec.rb95
-rw-r--r--spec/fixtures/markdown/markdown_golden_master_examples.yml14
-rw-r--r--spec/frontend/ide/components/ide_spec.js31
-rw-r--r--spec/frontend/ide/components/panes/collapsible_sidebar_spec.js26
-rw-r--r--spec/frontend/ide/components/panes/right_spec.js45
-rw-r--r--spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js214
-rw-r--r--spec/frontend/ide/stores/mutations_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js205
-rw-r--r--spec/frontend/vue_shared/components/markdown_drawer/mock_data.js42
-rw-r--r--spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js43
-rw-r--r--spec/helpers/ide_helper_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb22
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb19
-rw-r--r--spec/models/ci/processable_spec.rb14
-rw-r--r--spec/scripts/lib/glfm/update_specification_spec.rb6
16 files changed, 719 insertions, 71 deletions
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
index c7c740c2293..1f6d34efc0f 100644
--- a/spec/features/ide_spec.rb
+++ b/spec/features/ide_spec.rb
@@ -3,47 +3,90 @@
require 'spec_helper'
RSpec.describe 'IDE', :js do
- describe 'sub-groups' do
- let(:ide_iframe_selector) { '#ide iframe' }
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subgroup_project) { create(:project, :repository, namespace: subgroup) }
+ include WebIdeSpecHelpers
+
+ let_it_be(:ide_iframe_selector) { '#ide iframe' }
+ let_it_be(:normal_project) { create(:project, :repository) }
+
+ let(:project) { normal_project }
+ let(:vscode_ff) { false }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ stub_feature_flags(vscode_web_ide: vscode_ff)
+
+ sign_in(user)
+ end
+
+ shared_examples "legacy Web IDE" do
+ it 'loads legacy Web IDE', :aggregate_failures do
+ expect(page).to have_selector('.context-header', text: project.name)
+
+ # Assert new Web IDE is not loaded
+ expect(page).not_to have_selector(ide_iframe_selector)
+ end
+ end
+
+ shared_examples "new Web IDE" do
+ it 'loads new Web IDE', :aggregate_failures do
+ expect(page).not_to have_selector('.context-header')
+
+ iframe = find(ide_iframe_selector)
+
+ page.within_frame(iframe) do
+ expect(page).to have_selector('.title', text: project.name.upcase)
+ end
+ end
+ end
+ context 'with vscode feature flag off' do
before do
- stub_feature_flags(vscode_web_ide: vscode_ff)
- subgroup_project.add_maintainer(user)
- sign_in(user)
+ ide_visit(project)
+ end
- visit project_path(subgroup_project)
+ it_behaves_like 'legacy Web IDE'
- click_link('Web IDE')
+ it 'does not show switch button' do
+ expect(page).not_to have_button('Switch to new Web IDE')
+ end
+ end
+
+ context 'with vscode feature flag on and use_legacy_web_ide=true' do
+ let(:vscode_ff) { true }
+ let(:user) { create(:user, use_legacy_web_ide: true) }
- wait_for_requests
+ before do
+ ide_visit(project)
end
- context 'with vscode feature flag on' do
- let(:vscode_ff) { true }
+ it_behaves_like 'legacy Web IDE'
- it 'loads project in Web IDE' do
- iframe = find(ide_iframe_selector)
+ describe 'when user switches to new Web IDE' do
+ before do
+ click_button('Switch to new Web IDE')
- page.within_frame(iframe) do
- expect(page).to have_selector('.title', text: subgroup_project.name.upcase)
+ # Confirm modal
+ page.within('#confirmationModal') do
+ click_button('Switch editors')
end
end
+
+ it_behaves_like 'new Web IDE'
end
+ end
- context 'with vscode feature flag off' do
- let(:vscode_ff) { false }
+ describe 'sub-groups' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subgroup_project) { create(:project, :repository, namespace: subgroup) }
- it 'loads project in legacy Web IDE' do
- expect(page).to have_selector('.context-header', text: subgroup_project.name)
- end
+ let(:project) { subgroup_project }
- it 'does not load new Web IDE' do
- expect(page).not_to have_selector(ide_iframe_selector)
- end
+ before do
+ ide_visit(project)
end
+
+ it_behaves_like 'legacy Web IDE'
end
end
diff --git a/spec/fixtures/markdown/markdown_golden_master_examples.yml b/spec/fixtures/markdown/markdown_golden_master_examples.yml
index 6a1e75348cf..0c7e6ab5cd2 100644
--- a/spec/fixtures/markdown/markdown_golden_master_examples.yml
+++ b/spec/fixtures/markdown/markdown_golden_master_examples.yml
@@ -297,7 +297,7 @@
```
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
+ <pre data-sourcepos="1:1-3:3" lang="javascript" class="code highlight js-syntax-highlight language-javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
<copy-code></copy-code>
</div>
@@ -308,7 +308,7 @@
```
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"> plaintext</span></code></pre>
+ <pre data-sourcepos="1:1-3:3" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"> plaintext</span></code></pre>
<copy-code></copy-code>
</div>
@@ -319,7 +319,7 @@
```
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="foobar" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"> custom_language = &gt;&gt; this &lt;&lt;</span></code></pre>
+ <pre data-sourcepos="1:1-3:3" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="foobar" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"> custom_language = &gt;&gt; this &lt;&lt;</span></code></pre>
<copy-code></copy-code>
</div>
@@ -531,7 +531,7 @@
;;;
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-5:3" class="code highlight js-syntax-highlight language-json" lang="json" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="json"><span class="p">{</span></span>
+ <pre data-sourcepos="1:1-5:3" lang="json" class="code highlight js-syntax-highlight language-json" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="json"><span class="p">{</span></span>
<span id="LC2" class="line" lang="json"><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Page title"</span></span>
<span id="LC3" class="line" lang="json"><span class="p">}</span></span></code></pre>
<copy-code></copy-code>
@@ -544,7 +544,7 @@
+++
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-toml" lang="toml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="toml"><span class="py">title</span> <span class="p">=</span> <span class="s">"Page title"</span></span></code></pre>
+ <pre data-sourcepos="1:1-3:3" lang="toml" class="code highlight js-syntax-highlight language-toml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="toml"><span class="py">title</span> <span class="p">=</span> <span class="s">"Page title"</span></span></code></pre>
<copy-code></copy-code>
</div>
@@ -555,7 +555,7 @@
---
html: |-
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">Page title</span></span></code></pre>
+ <pre data-sourcepos="1:1-3:3" lang="yaml" class="code highlight js-syntax-highlight language-yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">Page title</span></span></code></pre>
<copy-code></copy-code>
</div>
@@ -690,7 +690,7 @@
<p data-sourcepos="1:1-1:36" dir="auto">This math is inline <code class="code math js-render-math" data-math-style="inline">a^2+b^2=c^2</code>.</p>
<p data-sourcepos="3:1-3:27" dir="auto">This is on a separate line:</p>
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="5:1-7:3" class="code highlight js-syntax-highlight language-math js-render-math" lang="math" v-pre="true" data-math-style="display"><code><span id="LC1" class="line" lang="math">a^2+b^2=c^2</span></code></pre>
+ <pre data-sourcepos="5:1-7:3" lang="math" data-math-style="display" class="js-render-math code highlight js-syntax-highlight language-math" v-pre="true"><code><span id="LC1" class="line" lang="math">a^2+b^2=c^2</span></code></pre>
<copy-code></copy-code>
</div>
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index 48c670757a2..a575f428a69 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -3,9 +3,11 @@ import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { stubPerformanceWebAPI } from 'helpers/performance';
+import { __ } from '~/locale';
import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue';
import ErrorMessage from '~/ide/components/error_message.vue';
import Ide from '~/ide/components/ide.vue';
+import eventHub from '~/ide/eventhub';
import { MSG_CANNOT_PUSH_CODE_GO_TO_FORK, MSG_GO_TO_FORK } from '~/ide/messages';
import { createStore } from '~/ide/stores';
import { file } from '../helpers';
@@ -14,6 +16,7 @@ import { projectData } from '../mock_data';
Vue.use(Vuex);
const TEST_FORK_IDE_PATH = '/test/ide/path';
+const MSG_ARE_YOU_SURE = __('Are you sure you want to lose unsaved changes?');
describe('WebIDE', () => {
const emptyProjData = { ...projectData, empty_repo: true, branches: {} };
@@ -40,6 +43,8 @@ describe('WebIDE', () => {
const findAlert = () => wrapper.findComponent(CannotPushCodeAlert);
+ const callOnBeforeUnload = (e = {}) => window.onbeforeunload(e);
+
beforeEach(() => {
stubPerformanceWebAPI();
@@ -49,6 +54,7 @@ describe('WebIDE', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ window.onbeforeunload = null;
});
describe('ide component, empty repo', () => {
@@ -90,7 +96,8 @@ describe('WebIDE', () => {
describe('onBeforeUnload', () => {
it('returns undefined when no staged files or changed files', () => {
createComponent();
- expect(wrapper.vm.onBeforeUnload()).toBe(undefined);
+
+ expect(callOnBeforeUnload()).toBe(undefined);
});
it('returns warning text when their are changed files', () => {
@@ -100,7 +107,10 @@ describe('WebIDE', () => {
},
});
- expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?');
+ const e = {};
+
+ expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE);
+ expect(e.returnValue).toBe(MSG_ARE_YOU_SURE);
});
it('returns warning text when their are staged files', () => {
@@ -110,20 +120,27 @@ describe('WebIDE', () => {
},
});
- expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?');
+ const e = {};
+
+ expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE);
+ expect(e.returnValue).toBe(MSG_ARE_YOU_SURE);
});
- it('updates event object', () => {
- const event = {};
+ it('returns undefined once after "skip-beforeunload" was emitted', () => {
createComponent({
state: {
stagedFiles: [file()],
},
});
- wrapper.vm.onBeforeUnload(event);
+ eventHub.$emit('skip-beforeunload');
+ const e = {};
+
+ expect(callOnBeforeUnload()).toBe(undefined);
+ expect(e.returnValue).toBe(undefined);
- expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?');
+ expect(callOnBeforeUnload(e)).toBe(MSG_ARE_YOU_SURE);
+ expect(e.returnValue).toBe(MSG_ARE_YOU_SURE);
});
});
diff --git a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
index 1d38231a767..e92f843ae6e 100644
--- a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
+++ b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
@@ -127,5 +127,29 @@ describe('ide/components/panes/collapsible_sidebar.vue', () => {
});
});
});
+
+ describe('with initOpenView that does not exist', () => {
+ beforeEach(async () => {
+ createComponent({ extensionTabs, initOpenView: 'does-not-exist' });
+
+ await nextTick();
+ });
+
+ it('nothing is dispatched', () => {
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with initOpenView that does exist', () => {
+ beforeEach(async () => {
+ createComponent({ extensionTabs, initOpenView: fakeView.name });
+
+ await nextTick();
+ });
+
+ it('dispatches open with view on create', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('rightPane/open', fakeView);
+ });
+ });
});
});
diff --git a/spec/frontend/ide/components/panes/right_spec.js b/spec/frontend/ide/components/panes/right_spec.js
index 4555f519bc2..b7349b8fed1 100644
--- a/spec/frontend/ide/components/panes/right_spec.js
+++ b/spec/frontend/ide/components/panes/right_spec.js
@@ -3,12 +3,16 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import RightPane from '~/ide/components/panes/right.vue';
+import SwitchEditorsView from '~/ide/components/switch_editors/switch_editors_view.vue';
import { rightSidebarViews } from '~/ide/constants';
import { createStore } from '~/ide/stores';
import extendStore from '~/ide/stores/extend';
+import { __ } from '~/locale';
Vue.use(Vuex);
+const SWITCH_EDITORS_VIEW_NAME = 'switch-editors';
+
describe('ide/components/panes/right.vue', () => {
let wrapper;
let store;
@@ -33,6 +37,19 @@ describe('ide/components/panes/right.vue', () => {
wrapper = null;
});
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders collapsible-sidebar', () => {
+ expect(wrapper.findComponent(CollapsibleSidebar).props()).toMatchObject({
+ side: 'right',
+ initOpenView: SWITCH_EDITORS_VIEW_NAME,
+ });
+ });
+ });
+
describe('pipelines tab', () => {
it('is always shown', () => {
createComponent();
@@ -113,4 +130,32 @@ describe('ide/components/panes/right.vue', () => {
);
});
});
+
+ describe('switch editors tab', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ desc | canUseNewWebIde | expectedShow
+ ${'is shown'} | ${true} | ${true}
+ ${'is not shown'} | ${false} | ${false}
+ `('with canUseNewWebIde=$canUseNewWebIde, $desc', async ({ canUseNewWebIde, expectedShow }) => {
+ Object.assign(store.state, { canUseNewWebIde });
+
+ await nextTick();
+
+ expect(wrapper.findComponent(CollapsibleSidebar).props('extensionTabs')).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ show: expectedShow,
+ title: __('Switch editors'),
+ views: [
+ { component: SwitchEditorsView, name: SWITCH_EDITORS_VIEW_NAME, keepAlive: true },
+ ],
+ }),
+ ]),
+ );
+ });
+ });
});
diff --git a/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js b/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js
new file mode 100644
index 00000000000..7a958391fea
--- /dev/null
+++ b/spec/frontend/ide/components/switch_editors/switch_editors_view_spec.js
@@ -0,0 +1,214 @@
+import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import { createAlert } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { logError } from '~/lib/logger';
+import { __ } from '~/locale';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import SwitchEditorsView, {
+ MSG_ERROR_ALERT,
+ MSG_CONFIRM,
+ MSG_TITLE,
+ MSG_LEARN_MORE,
+ MSG_DESCRIPTION,
+} from '~/ide/components/switch_editors/switch_editors_view.vue';
+import eventHub from '~/ide/eventhub';
+import { createStore } from '~/ide/stores';
+
+jest.mock('~/flash');
+jest.mock('~/lib/logger');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
+
+const TEST_USER_PREFERENCES_PATH = '/test/user-pref/path';
+const TEST_SWITCH_EDITOR_SVG_PATH = '/test/switch/editor/path.svg';
+const TEST_HREF = '/test/new/web/ide/href';
+
+describe('~/ide/components/switch_editors/switch_editors_view.vue', () => {
+ useMockLocationHelper();
+
+ let store;
+ let wrapper;
+ let confirmResolve;
+ let requestSpy;
+ let skipBeforeunloadSpy;
+ let axiosMock;
+
+ // region: finders ------------------
+ const findButton = () => wrapper.findComponent(GlButton);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ // region: actions ------------------
+ const triggerSwitchPreference = () => findButton().vm.$emit('click');
+ const submitConfirm = async (val) => {
+ confirmResolve(val);
+
+ // why: We need to wait for promises for the immediate next lines to be executed
+ await waitForPromises();
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(SwitchEditorsView, {
+ store,
+ stubs: {
+ GlEmptyState,
+ },
+ });
+ };
+
+ // region: test setup ------------------
+ beforeEach(() => {
+ // Setup skip-beforeunload side-effect
+ skipBeforeunloadSpy = jest.fn();
+ eventHub.$on('skip-beforeunload', skipBeforeunloadSpy);
+
+ // Setup request side-effect
+ requestSpy = jest.fn().mockImplementation(() => new Promise(() => {}));
+ axiosMock = new MockAdapter(axios);
+ axiosMock.onPut(TEST_USER_PREFERENCES_PATH).reply(({ data }) => requestSpy(data));
+
+ // Setup store
+ store = createStore();
+ store.state.userPreferencesPath = TEST_USER_PREFERENCES_PATH;
+ store.state.switchEditorSvgPath = TEST_SWITCH_EDITOR_SVG_PATH;
+ store.state.links = {
+ newWebIDEHelpPagePath: TEST_HREF,
+ };
+
+ // Setup user confirm side-effect
+ confirmAction.mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ confirmResolve = resolve;
+ }),
+ );
+ });
+
+ afterEach(() => {
+ eventHub.$off('skip-beforeunload', skipBeforeunloadSpy);
+
+ axiosMock.restore();
+ });
+
+ // region: tests ------------------
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('render empty state', () => {
+ expect(findEmptyState().props()).toMatchObject({
+ svgPath: TEST_SWITCH_EDITOR_SVG_PATH,
+ svgHeight: 150,
+ title: MSG_TITLE,
+ });
+ });
+
+ it('render link', () => {
+ expect(wrapper.findComponent(GlLink).attributes('href')).toBe(TEST_HREF);
+ expect(wrapper.findComponent(GlLink).text()).toBe(MSG_LEARN_MORE);
+ });
+
+ it('renders description', () => {
+ expect(findEmptyState().text()).toContain(MSG_DESCRIPTION);
+ });
+
+ it('is not loading', () => {
+ expect(findButton().props('loading')).toBe(false);
+ });
+ });
+
+ describe('when user triggers switch preference', () => {
+ beforeEach(() => {
+ createComponent();
+
+ triggerSwitchPreference();
+ });
+
+ it('creates a single confirm', () => {
+ // Call again to ensure that we only show 1 confirm action
+ triggerSwitchPreference();
+
+ expect(confirmAction).toHaveBeenCalledTimes(1);
+ expect(confirmAction).toHaveBeenCalledWith(MSG_CONFIRM, {
+ primaryBtnText: __('Switch editors'),
+ cancelBtnText: __('Cancel'),
+ });
+ });
+
+ it('starts loading', () => {
+ expect(findButton().props('loading')).toBe(true);
+ });
+
+ describe('when user cancels confirm', () => {
+ beforeEach(async () => {
+ await submitConfirm(false);
+ });
+
+ it('does not make request', () => {
+ expect(requestSpy).not.toHaveBeenCalled();
+ });
+
+ it('can be triggered again', () => {
+ triggerSwitchPreference();
+
+ expect(confirmAction).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ describe('when user accepts confirm and response success', () => {
+ beforeEach(async () => {
+ requestSpy.mockReturnValue([200, {}]);
+ await submitConfirm(true);
+ });
+
+ it('does not handle error', () => {
+ expect(logError).not.toHaveBeenCalled();
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
+ it('emits "skip-beforeunload" and reloads', () => {
+ expect(skipBeforeunloadSpy).toHaveBeenCalledTimes(1);
+ expect(window.location.reload).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls request', () => {
+ expect(requestSpy).toHaveBeenCalledTimes(1);
+ expect(requestSpy).toHaveBeenCalledWith(
+ JSON.stringify({ user: { use_legacy_web_ide: false } }),
+ );
+ });
+
+ it('is not loading', () => {
+ expect(findButton().props('loading')).toBe(false);
+ });
+ });
+
+ describe('when user accepts confirm and response fails', () => {
+ beforeEach(async () => {
+ requestSpy.mockReturnValue([400, {}]);
+ await submitConfirm(true);
+ });
+
+ it('handles error', () => {
+ expect(logError).toHaveBeenCalledTimes(1);
+ expect(logError).toHaveBeenCalledWith(
+ 'Error while updating user preferences',
+ expect.any(Error),
+ );
+
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledWith({
+ message: MSG_ERROR_ALERT,
+ });
+ });
+
+ it('does not reload', () => {
+ expect(skipBeforeunloadSpy).not.toHaveBeenCalled();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js
index 4117f2648bd..ae21d257bb2 100644
--- a/spec/frontend/ide/stores/mutations_spec.js
+++ b/spec/frontend/ide/stores/mutations_spec.js
@@ -87,11 +87,13 @@ describe('Multi-file store mutations', () => {
emptyStateSvgPath: 'emptyState',
noChangesStateSvgPath: 'noChanges',
committedStateSvgPath: 'committed',
+ switchEditorSvgPath: 'switchEditorSvg',
});
expect(localState.emptyStateSvgPath).toBe('emptyState');
expect(localState.noChangesStateSvgPath).toBe('noChanges');
expect(localState.committedStateSvgPath).toBe('committed');
+ expect(localState.switchEditorSvgPath).toBe('switchEditorSvg');
});
});
diff --git a/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js
new file mode 100644
index 00000000000..8edcb905096
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js
@@ -0,0 +1,205 @@
+import { GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import MarkdownDrawer, { cache } from '~/vue_shared/components/markdown_drawer/markdown_drawer.vue';
+import { getRenderedMarkdown } from '~/vue_shared/components/markdown_drawer/utils/fetch';
+import { contentTop } from '~/lib/utils/common_utils';
+
+jest.mock('~/vue_shared/components/markdown_drawer/utils/fetch', () => ({
+ getRenderedMarkdown: jest.fn().mockReturnValue({
+ title: 'test title test',
+ body: `<div id="content-body">
+ <div class="documentation md gl-mt-3">
+ test body
+ </div>
+ </div>`,
+ }),
+}));
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ contentTop: jest.fn(),
+}));
+
+describe('MarkdownDrawer', () => {
+ let wrapper;
+ const defaultProps = {
+ documentPath: 'user/search/global_search/advanced_search_syntax.json',
+ };
+
+ const createComponent = (props) => {
+ wrapper = shallowMountExtended(MarkdownDrawer, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ Object.keys(cache).forEach((key) => delete cache[key]);
+ });
+
+ const findDrawer = () => wrapper.findComponent(GlDrawer);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader);
+ const findDrawerTitle = () => wrapper.findComponent('[data-testid="title-element"]');
+ const findDrawerBody = () => wrapper.findComponent({ ref: 'content-element' });
+
+ describe('component', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders correctly', () => {
+ expect(findDrawer().exists()).toBe(true);
+ expect(findDrawerTitle().text()).toBe('test title test');
+ expect(findDrawerBody().text()).toBe('test body');
+ });
+ });
+
+ describe.each`
+ hasNavbar | navbarHeight
+ ${false} | ${0}
+ ${true} | ${100}
+ `('computes offsetTop', ({ hasNavbar, navbarHeight }) => {
+ beforeEach(() => {
+ global.document.querySelector = jest.fn(() =>
+ hasNavbar
+ ? {
+ dataset: {
+ page: 'test',
+ },
+ }
+ : undefined,
+ );
+ contentTop.mockReturnValue(navbarHeight);
+ createComponent();
+ });
+
+ afterEach(() => {
+ contentTop.mockClear();
+ });
+
+ it(`computes offsetTop ${hasNavbar ? 'with' : 'without'} .navbar-gitlab`, () => {
+ expect(findDrawer().attributes('headerheight')).toBe(`${navbarHeight}px`);
+ });
+ });
+
+ describe('watcher', () => {
+ let renderGLFMSpy;
+ let fetchMarkdownSpy;
+
+ beforeEach(async () => {
+ renderGLFMSpy = jest.spyOn(MarkdownDrawer.methods, 'renderGLFM');
+ fetchMarkdownSpy = jest.spyOn(MarkdownDrawer.methods, 'fetchMarkdown');
+ global.document.querySelector = jest.fn(() => ({
+ getBoundingClientRect: jest.fn(() => ({ bottom: 100 })),
+ dataset: {
+ page: 'test',
+ },
+ }));
+ createComponent();
+ await nextTick();
+ });
+
+ afterEach(() => {
+ renderGLFMSpy.mockClear();
+ fetchMarkdownSpy.mockClear();
+ });
+
+ it('for documentPath triggers fetch', async () => {
+ expect(fetchMarkdownSpy).toHaveBeenCalledTimes(1);
+
+ await wrapper.setProps({ documentPath: '/test/me' });
+ await nextTick();
+
+ expect(fetchMarkdownSpy).toHaveBeenCalledTimes(2);
+ });
+
+ it('for open triggers renderGLFM', async () => {
+ wrapper.vm.fetchMarkdown();
+ wrapper.vm.openDrawer();
+ await nextTick();
+ expect(renderGLFMSpy).toHaveBeenCalled();
+ });
+ });
+
+ describe('Markdown fetching', () => {
+ let renderGLFMSpy;
+
+ beforeEach(async () => {
+ renderGLFMSpy = jest.spyOn(MarkdownDrawer.methods, 'renderGLFM');
+ createComponent();
+ await nextTick();
+ });
+
+ afterEach(() => {
+ renderGLFMSpy.mockClear();
+ });
+
+ it('fetches the Markdown and caches it', async () => {
+ expect(getRenderedMarkdown).toHaveBeenCalledTimes(1);
+ expect(Object.keys(cache)).toHaveLength(1);
+ });
+
+ it('when the document changes, fetches it and caches it as well', async () => {
+ expect(getRenderedMarkdown).toHaveBeenCalledTimes(1);
+ expect(Object.keys(cache)).toHaveLength(1);
+
+ await wrapper.setProps({ documentPath: '/test/me2' });
+ await nextTick();
+
+ expect(getRenderedMarkdown).toHaveBeenCalledTimes(2);
+ expect(Object.keys(cache)).toHaveLength(2);
+ });
+
+ it('when re-using an already fetched document, gets it from the cache', async () => {
+ await wrapper.setProps({ documentPath: '/test/me2' });
+ await nextTick();
+
+ expect(getRenderedMarkdown).toHaveBeenCalledTimes(2);
+ expect(Object.keys(cache)).toHaveLength(2);
+
+ await wrapper.setProps({ documentPath: defaultProps.documentPath });
+ await nextTick();
+
+ expect(getRenderedMarkdown).toHaveBeenCalledTimes(2);
+ expect(Object.keys(cache)).toHaveLength(2);
+ });
+ });
+
+ describe('Markdown fetching returns error', () => {
+ beforeEach(async () => {
+ getRenderedMarkdown.mockReturnValue({
+ hasFetchError: true,
+ });
+
+ createComponent();
+ await nextTick();
+ });
+ afterEach(() => {
+ getRenderedMarkdown.mockClear();
+ });
+ it('shows alert', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+ });
+
+ describe('While Markdown is fetching', () => {
+ beforeEach(async () => {
+ getRenderedMarkdown.mockReturnValue(new Promise(() => {}));
+
+ createComponent();
+ });
+
+ afterEach(() => {
+ getRenderedMarkdown.mockClear();
+ });
+
+ it('shows skeleton', async () => {
+ expect(findSkeleton().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js b/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js
new file mode 100644
index 00000000000..53b40407556
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js
@@ -0,0 +1,42 @@
+export const MOCK_HTML = `<!DOCTYPE html>
+<html>
+<body>
+ <div id="content-body">
+ <h1>test title <strong>test</strong></h1>
+ <div class="documentation md gl-mt-3">
+ <a href="../advanced_search.md">Advanced Search</a>
+ <a href="../advanced_search2.md">Advanced Search2</a>
+ <h2>test header h2</h2>
+ <table class="testClass">
+ <tr>
+ <td>Emil</td>
+ <td>Tobias</td>
+ <td>Linus</td>
+ </tr>
+ <tr>
+ <td>16</td>
+ <td>14</td>
+ <td>10</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</body>
+</html>`.replace(/\n/g, '');
+
+export const MOCK_DRAWER_DATA = {
+ hasFetchError: false,
+ title: 'test title test',
+ body: ` <div id="content-body"> <div class="documentation md gl-mt-3"> <a href="../advanced_search.md">Advanced Search</a> <a href="../advanced_search2.md">Advanced Search2</a> <h2>test header h2</h2> <table class="testClass"> <tbody><tr> <td>Emil</td> <td>Tobias</td> <td>Linus</td> </tr> <tr> <td>16</td> <td>14</td> <td>10</td> </tr> </tbody></table> </div> </div>`,
+};
+
+export const MOCK_DRAWER_DATA_ERROR = {
+ hasFetchError: true,
+};
+
+export const MOCK_TABLE_DATA_BEFORE = `<head></head><body><h1>test</h1></test><table><tbody><tr><td></td></tr></tbody></table></body>`;
+
+export const MOCK_HTML_DATA_AFTER = {
+ body: '<table><tbody><tr><td></td></tr></tbody></table>',
+ title: 'test',
+};
diff --git a/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js b/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js
new file mode 100644
index 00000000000..ff07b2cf838
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js
@@ -0,0 +1,43 @@
+import MockAdapter from 'axios-mock-adapter';
+import {
+ getRenderedMarkdown,
+ splitDocument,
+} from '~/vue_shared/components/markdown_drawer/utils/fetch';
+import axios from '~/lib/utils/axios_utils';
+import {
+ MOCK_HTML,
+ MOCK_DRAWER_DATA,
+ MOCK_DRAWER_DATA_ERROR,
+ MOCK_TABLE_DATA_BEFORE,
+ MOCK_HTML_DATA_AFTER,
+} from '../mock_data';
+
+describe('utils/fetch', () => {
+ let mock;
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe.each`
+ axiosMock | type | toExpect
+ ${{ code: 200, res: { html: MOCK_HTML } }} | ${'success'} | ${MOCK_DRAWER_DATA}
+ ${{ code: 500, res: null }} | ${'error'} | ${MOCK_DRAWER_DATA_ERROR}
+ `('process markdown data', ({ axiosMock, type, toExpect }) => {
+ describe(`if api fetch responds with ${type}`, () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onGet().reply(axiosMock.code, axiosMock.res);
+ });
+ it(`should update drawer correctly`, async () => {
+ expect(await getRenderedMarkdown('/any/path')).toStrictEqual(toExpect);
+ });
+ });
+ });
+
+ describe('splitDocument', () => {
+ it(`should update tables correctly`, () => {
+ expect(splitDocument(MOCK_TABLE_DATA_BEFORE)).toStrictEqual(MOCK_HTML_DATA_AFTER);
+ });
+ });
+});
diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb
index e750379f62d..447967fd345 100644
--- a/spec/helpers/ide_helper_spec.rb
+++ b/spec/helpers/ide_helper_spec.rb
@@ -26,6 +26,8 @@ RSpec.describe IdeHelper do
'can-use-new-web-ide' => 'true',
'use-new-web-ide' => 'true',
'user-preferences-path' => profile_preferences_path,
+ 'new-web-ide-help-page-path' =>
+ help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'),
'branch-name' => 'master',
'project-path' => project.path_with_namespace,
'csp-nonce' => 'test-csp-nonce'
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index 350ac1edbe1..c5d2bcd5363 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -111,35 +111,35 @@ RSpec.describe Banzai::Filter::MathFilter do
describe 'display math using ```math...``` syntax' do
it 'adds data-math-style display attribute to display math' do
- doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>')
+ doc = filter('<pre lang="math"><code>2+2</code></pre>')
pre = doc.xpath('descendant-or-self::pre').first
expect(pre['data-math-style']).to eq 'display'
end
it 'adds js-render-math class to display math' do
- doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>')
+ doc = filter('<pre lang="math"><code>2+2</code></pre>')
pre = doc.xpath('descendant-or-self::pre').first
expect(pre[:class]).to include("js-render-math")
end
it 'ignores code blocks that are not math' do
- input = '<pre class="code highlight js-syntax-highlight language-plaintext" v-pre="true"><code>2+2</code></pre>'
+ input = '<pre lang="plaintext"><code>2+2</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq input
end
it 'requires the pre to contain both code and math' do
- input = '<pre class="highlight js-syntax-highlight language-plaintext language-math" v-pre="true"><code>2+2</code></pre>'
+ input = '<pre lang="math">something</pre>'
doc = filter(input)
expect(doc.to_s).to eq input
end
it 'dollar signs around to display math' do
- doc = filter('$<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>$')
+ doc = filter('$<pre lang="math"><code>2+2</code></pre>$')
before = doc.xpath('descendant-or-self::text()[1]').first
after = doc.xpath('descendant-or-self::text()[3]').first
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 33adca0ddfc..d188a40c53e 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", ""
@@ -31,9 +31,9 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
context "when contains mermaid diagrams" do
it "ignores mermaid blocks" do
- result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
+ result = filter('<pre data-mermaid-style="display" lang="mermaid"><code class="js-render-mermaid">mermaid code</code></pre>')
- expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-mermaid-style="display" lang="mermaid" class="code highlight js-syntax-highlight language-mermaid" v-pre="true"><code class="js-render-mermaid"><span id="LC1" class="line" lang="mermaid">mermaid code</span></code></pre><copy-code></copy-code></div>')
end
end
@@ -49,7 +49,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as that language" do
result = filter('<pre lang="ruby"><code>def fun end</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="ruby" class="code highlight js-syntax-highlight language-ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
@@ -59,7 +59,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext" do
result = filter('<pre lang="gnuplot"><code>This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="gnuplot" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="gnuplot" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "gnuplot"
@@ -74,7 +74,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>})
- expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
+ expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
@@ -87,7 +87,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "includes data-lang-params tag with extra information" do
result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>})
- expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
+ expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>})
end
include_examples "XSS prevention", lang
@@ -105,7 +105,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
let(:lang_params) { '-1+10' }
let(:expected_result) do
- %{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}
+ %{<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="#{lang}" class="code highlight js-syntax-highlight language-#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}
end
context 'when delimiter is space' do
@@ -130,13 +130,13 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "includes it in the highlighted code block" do
result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code lang="plaintext"><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end
it "escape sourcepos metadata to prevent XSS" do
result = filter('<pre data-sourcepos="&#34;%22 href=&#34;x&#34;></pre><base href=http://unsafe-website.com/><pre x=&#34;"><code></code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos=\'"%22 href="x"&gt;&lt;/pre&gt;&lt;base href=http://unsafe-website.com/&gt;&lt;pre x="\' class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos=\'"%22 href="x"&gt;&lt;/pre&gt;&lt;base href=http://unsafe-website.com/&gt;&lt;pre x="\' lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
end
end
@@ -150,7 +150,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext" do
result = filter('<pre lang="ruby"><code>This is a test</code></pre>')
- expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight" lang="" data-canonical-lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
+ expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre lang="" class="code highlight js-syntax-highlight" data-canonical-lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>')
end
include_examples "XSS prevention", "ruby"
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 6dfc6abe965..d2eb9209f42 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -94,9 +94,18 @@ module Gitlab
# Move this test back to the items hash when removing `use_cmark_renderer` feature flag.
it "does not convert dangerous fenced code with inline script into HTML" do
input = '```mypre"><script>alert(3)</script>'
- output = "<div>\n<div>\n<div class=\"gl-relative markdown-code-block js-markdown-code\">\n<pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" data-canonical-lang=\"mypre\" v-pre=\"true\"><code></code></pre>\n<copy-code></copy-code>\n</div>\n</div>\n</div>"
+ output = <<~HTML
+ <div>
+ <div>
+ <div class=\"gl-relative markdown-code-block js-markdown-code\">
+ <pre lang=\"plaintext\" class=\"code highlight js-syntax-highlight language-plaintext\" data-canonical-lang=\"mypre\" v-pre=\"true\"><code></code></pre>
+ <copy-code></copy-code>
+ </div>
+ </div>
+ </div>
+ HTML
- expect(render(input, context)).to include(output)
+ expect(render(input, context)).to include(output.strip)
end
it 'does not allow locked attributes to be overridden' do
@@ -360,7 +369,7 @@ module Gitlab
<div>
<div>
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" data-canonical-lang="js" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
+ <pre lang="javascript" class="code highlight js-syntax-highlight language-javascript" data-canonical-lang="js" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
<copy-code></copy-code>
</div>
</div>
@@ -390,7 +399,7 @@ module Gitlab
<div>class.cpp</div>
<div>
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre class="code highlight js-syntax-highlight language-cpp" lang="cpp" data-canonical-lang="c++" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span></span>
+ <pre lang="cpp" class="code highlight js-syntax-highlight language-cpp" data-canonical-lang="c++" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span></span>
<span id="LC2" class="line" lang="cpp"></span>
<span id="LC3" class="line" lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span></span>
<span id="LC4" class="line" lang="cpp"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"*"</span><span class="o">&lt;&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span></span>
@@ -448,7 +457,7 @@ module Gitlab
stem:[2+2] is 4
MD
- expect(render(input, context)).to include('<pre data-math-style="display" class="code math js-render-math"><code>eta_x gamma</code></pre>')
+ expect(render(input, context)).to include('<pre data-math-style="display" lang="plaintext" class="code math js-render-math" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">eta_x gamma</span></code></pre>')
expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
end
end
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index a199111b1e3..261eba73424 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -52,7 +52,11 @@ RSpec.describe Ci::Processable do
let_it_be(:internal_job_variable) { create(:ci_job_variable, job: processable) }
- let(:clone_accessors) { ::Ci::Build.clone_accessors.without(::Ci::Build.extra_accessors) }
+ let(:clone_accessors) do
+ %i[pipeline project ref tag options name allow_failure stage stage_idx trigger_request yaml_variables
+ when environment coverage_regex description tag_list protected needs_attributes job_variables_attributes
+ resource_group scheduling_type ci_stage partition_id id_tokens]
+ end
let(:reject_accessors) do
%i[id status user token_encrypted coverage runner artifacts_expire_at
@@ -83,7 +87,8 @@ RSpec.describe Ci::Processable do
resource resource_group_id processed security_scans author
pipeline_id report_results pending_state pages_deployments
queuing_entry runtime_metadata trace_metadata
- dast_site_profile dast_scanner_profile stage_id].freeze
+ dast_site_profile dast_scanner_profile stage_id dast_site_profiles_build
+ dast_scanner_profiles_build].freeze
end
before_all do
@@ -177,10 +182,7 @@ RSpec.describe Ci::Processable do
Ci::Build.attribute_names.map(&:to_sym) +
Ci::Build.attribute_aliases.keys.map(&:to_sym) +
Ci::Build.reflect_on_all_associations.map(&:name) +
- [:tag_list, :needs_attributes, :job_variables_attributes, :id_tokens] -
- # ToDo: Move EE accessors to ee/
- ::Ci::Build.extra_accessors -
- [:dast_site_profiles_build, :dast_scanner_profiles_build]
+ [:tag_list, :needs_attributes, :job_variables_attributes, :id_tokens]
current_accessors.uniq!
diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb
index 8a7d7100363..3798286c0c4 100644
--- a/spec/scripts/lib/glfm/update_specification_spec.rb
+++ b/spec/scripts/lib/glfm/update_specification_spec.rb
@@ -292,7 +292,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
expected_spec_html = <<~RENDERED_HTML
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-4:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">GitLab Flavored Markdown (GLFM) Spec</span></span>
+ <pre data-sourcepos="1:1-4:3" lang="yaml" class="code highlight js-syntax-highlight language-yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">GitLab Flavored Markdown (GLFM) Spec</span></span>
<span id="LC2" class="line" lang="yaml"><span class="na">version</span><span class="pi">:</span> <span class="s">alpha</span></span></code></pre>
<copy-code></copy-code>
</div>
@@ -311,7 +311,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
expected_snapshot_spec_html = <<~RENDERED_HTML
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="1:1-4:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">GitLab Flavored Markdown (GLFM) Spec</span></span>
+ <pre data-sourcepos="1:1-4:3" lang="yaml" class="code highlight js-syntax-highlight language-yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">GitLab Flavored Markdown (GLFM) Spec</span></span>
<span id="LC2" class="line" lang="yaml"><span class="na">version</span><span class="pi">:</span> <span class="s">alpha</span></span></code></pre>
<copy-code></copy-code>
</div>
@@ -320,7 +320,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do
<h2 data-sourcepos="7:1-7:9" dir="auto">
<a id="user-content-strong" class="anchor" href="#strong" aria-hidden="true"></a>Strong</h2>
<div class="gl-relative markdown-code-block js-markdown-code">
- <pre data-sourcepos="9:1-13:32" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">__bold__</span>
+ <pre data-sourcepos="9:1-13:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">__bold__</span>
<span id="LC2" class="line" lang="plaintext">.</span>
<span id="LC3" class="line" lang="plaintext">&lt;p&gt;&lt;strong&gt;bold&lt;/strong&gt;&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>