diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-02 09:09:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-02 09:09:00 +0300 |
commit | 03c509e17bfa71a124a69b6cf4897414d3bd1cb5 (patch) | |
tree | 1301bca7b00224dd4f567d7e43344982a135c14f /spec | |
parent | 455e6650ee300263e78a8142d247f538c59737a6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 = >> this <<</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 = >> this <<</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=""%22 href="x"></pre><base href=http://unsafe-website.com/><pre x=""><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"></pre><base href=http://unsafe-website.com/><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"></pre><base href=http://unsafe-website.com/><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"><stdio.h></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"><stdio.h></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"><</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"><<</span><span class="s">"*"</span><span class="o"><<</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"><p><strong>bold</strong></p></span></code></pre> <copy-code></copy-code> |