Commit d8458c4d authored by Himanshu Kapoor's avatar Himanshu Kapoor Committed by Ash McKenzie

Consolidate the Edit and Web IDE buttons

parent 49ed45ff
...@@ -3,6 +3,7 @@ import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_sta ...@@ -3,6 +3,7 @@ import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_sta
import BlobViewer from '~/blob/viewer/index'; import BlobViewer from '~/blob/viewer/index';
import initBlob from '~/pages/projects/init_blob'; import initBlob from '~/pages/projects/init_blob';
import GpgBadges from '~/gpg_badges'; import GpgBadges from '~/gpg_badges';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import '~/sourcegraph/load'; import '~/sourcegraph/load';
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue'; import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
...@@ -55,6 +56,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -55,6 +56,8 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
} }
initWebIdeLink({ el: document.getElementById('js-blob-web-ide-link') });
GpgBadges.fetch(); GpgBadges.fetch();
const codeNavEl = document.getElementById('js-code-navigation'); const codeNavEl = document.getElementById('js-code-navigation');
......
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { joinPaths, webIDEUrl } from '~/lib/utils/url_utility';
import WebIdeButton from '~/vue_shared/components/web_ide_link.vue';
export default ({ el, router }) => {
if (!el) return;
const { projectPath, ref, isBlob, webIdeUrl, ...options } = convertObjectPropsToCamelCase(
JSON.parse(el.dataset.options),
);
// eslint-disable-next-line no-new
new Vue({
el,
router,
render(h) {
return h(WebIdeButton, {
props: {
isBlob,
webIdeUrl: isBlob
? webIdeUrl
: webIDEUrl(
joinPaths('/', projectPath, 'edit', ref, '-', this.$route?.params.path || '', '/'),
),
...options,
},
});
},
});
};
import Vue from 'vue'; import Vue from 'vue';
import PathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql'; import PathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { escapeFileUrl, joinPaths, webIDEUrl } from '../lib/utils/url_utility'; import { escapeFileUrl } from '../lib/utils/url_utility';
import createRouter from './router'; import createRouter from './router';
import App from './components/app.vue'; import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue'; import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue'; import LastCommit from './components/last_commit.vue';
import TreeActionLink from './components/tree_action_link.vue'; import TreeActionLink from './components/tree_action_link.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import DirectoryDownloadLinks from './components/directory_download_links.vue'; import DirectoryDownloadLinks from './components/directory_download_links.vue';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { setTitle } from './utils/title'; import { setTitle } from './utils/title';
import { updateFormAction } from './utils/dom'; import { updateFormAction } from './utils/dom';
import { convertObjectPropsToCamelCase, parseBoolean } from '../lib/utils/common_utils'; import { parseBoolean } from '../lib/utils/common_utils';
import { __ } from '../locale'; import { __ } from '../locale';
export default function setupVueRepositoryList() { export default function setupVueRepositoryList() {
...@@ -138,31 +138,7 @@ export default function setupVueRepositoryList() { ...@@ -138,31 +138,7 @@ export default function setupVueRepositoryList() {
}, },
}); });
const webIdeLinkEl = document.getElementById('js-tree-web-ide-link'); initWebIdeLink({ el: document.getElementById('js-tree-web-ide-link'), router });
if (webIdeLinkEl) {
const {
webIdeUrlData: { path: ideBasePath, isFork: webIdeIsFork },
...options
} = convertObjectPropsToCamelCase(JSON.parse(webIdeLinkEl.dataset.options), { deep: true });
// eslint-disable-next-line no-new
new Vue({
el: webIdeLinkEl,
router,
render(h) {
return h(WebIdeLink, {
props: {
webIdeUrl: webIDEUrl(
joinPaths('/', ideBasePath, 'edit', ref, '-', this.$route.params.path || '', '/'),
),
webIdeIsFork,
...options,
},
});
},
});
}
const directoryDownloadLinks = document.getElementById('js-directory-downloads'); const directoryDownloadLinks = document.getElementById('js-directory-downloads');
......
...@@ -3,7 +3,7 @@ import { ...@@ -3,7 +3,7 @@ import {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownDivider, GlDropdownDivider,
GlLink, GlButton,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -12,7 +12,7 @@ export default { ...@@ -12,7 +12,7 @@ export default {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownDivider, GlDropdownDivider,
GlLink, GlButton,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -27,6 +27,16 @@ export default { ...@@ -27,6 +27,16 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
category: {
type: String,
required: false,
default: 'secondary',
},
variant: {
type: String,
required: false,
default: 'default',
},
}, },
computed: { computed: {
hasMultipleActions() { hasMultipleActions() {
...@@ -54,6 +64,8 @@ export default { ...@@ -54,6 +64,8 @@ export default {
class="gl-button-deprecated-adapter" class="gl-button-deprecated-adapter"
:text="selectedAction.text" :text="selectedAction.text"
:split-href="selectedAction.href" :split-href="selectedAction.href"
:variant="variant"
:category="category"
split split
@click="handleClick(selectedAction, $event)" @click="handleClick(selectedAction, $event)"
> >
...@@ -77,14 +89,15 @@ export default { ...@@ -77,14 +89,15 @@ export default {
<gl-dropdown-divider v-if="index != actions.length - 1" :key="action.key + '_divider'" /> <gl-dropdown-divider v-if="index != actions.length - 1" :key="action.key + '_divider'" />
</template> </template>
</gl-dropdown> </gl-dropdown>
<gl-link <gl-button
v-else-if="selectedAction" v-else-if="selectedAction"
v-gl-tooltip="selectedAction.tooltip" v-gl-tooltip="selectedAction.tooltip"
v-bind="selectedAction.attrs" v-bind="selectedAction.attrs"
class="btn" :variant="variant"
:category="category"
:href="selectedAction.href" :href="selectedAction.href"
@click="handleClick(selectedAction, $event)" @click="handleClick(selectedAction, $event)"
> >
{{ selectedAction.text }} {{ selectedAction.text }}
</gl-link> </gl-button>
</template> </template>
...@@ -4,6 +4,7 @@ import { __ } from '~/locale'; ...@@ -4,6 +4,7 @@ import { __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue'; import ActionsButton from '~/vue_shared/components/actions_button.vue';
const KEY_EDIT = 'edit';
const KEY_WEB_IDE = 'webide'; const KEY_WEB_IDE = 'webide';
const KEY_GITPOD = 'gitpod'; const KEY_GITPOD = 'gitpod';
...@@ -13,21 +14,31 @@ export default { ...@@ -13,21 +14,31 @@ export default {
LocalStorageSync, LocalStorageSync,
}, },
props: { props: {
webIdeUrl: { isFork: {
type: String, type: Boolean,
required: false, required: false,
default: '', default: false,
},
needsToFork: {
type: Boolean,
required: false,
default: false,
}, },
webIdeIsFork: { gitpodEnabled: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
needsToFork: { isBlob: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
showEditButton: {
type: Boolean,
required: false,
default: true,
},
showWebIdeButton: { showWebIdeButton: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -38,15 +49,20 @@ export default { ...@@ -38,15 +49,20 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
gitpodUrl: { editUrl: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
gitpodEnabled: { webIdeUrl: {
type: Boolean, type: String,
required: false, required: false,
default: false, default: '',
},
gitpodUrl: {
type: String,
required: false,
default: '',
}, },
}, },
data() { data() {
...@@ -56,7 +72,33 @@ export default { ...@@ -56,7 +72,33 @@ export default {
}, },
computed: { computed: {
actions() { actions() {
return [this.webIdeAction, this.gitpodAction].filter(x => x); return [this.webIdeAction, this.editAction, this.gitpodAction].filter(action => action);
},
editAction() {
if (!this.showEditButton) {
return null;
}
const handleOptions = this.needsToFork
? {
href: '#modal-confirm-fork-edit',
handle: () => this.showModal('#modal-confirm-fork-edit'),
}
: { href: this.editUrl };
return {
key: KEY_EDIT,
text: __('Edit'),
secondaryText: __('Edit this file only.'),
tooltip: '',
attrs: {
'data-qa-selector': 'edit_button',
'data-track-event': 'click_edit',
// eslint-disable-next-line @gitlab/require-i18n-strings
'data-track-label': 'Edit',
},
...handleOptions,
};
}, },
webIdeAction() { webIdeAction() {
if (!this.showWebIdeButton) { if (!this.showWebIdeButton) {
...@@ -64,10 +106,19 @@ export default { ...@@ -64,10 +106,19 @@ export default {
} }
const handleOptions = this.needsToFork const handleOptions = this.needsToFork
? { href: '#modal-confirm-fork', handle: () => this.showModal('#modal-confirm-fork') } ? {
href: '#modal-confirm-fork-webide',
handle: () => this.showModal('#modal-confirm-fork-webide'),
}
: { href: this.webIdeUrl }; : { href: this.webIdeUrl };
const text = this.webIdeIsFork ? __('Edit fork in Web IDE') : __('Web IDE'); let text = __('Web IDE');
if (this.isBlob) {
text = __('Edit in Web IDE');
} else if (this.isFork) {
text = __('Edit fork in Web IDE');
}
return { return {
key: KEY_WEB_IDE, key: KEY_WEB_IDE,
...@@ -76,6 +127,9 @@ export default { ...@@ -76,6 +127,9 @@ export default {
tooltip: '', tooltip: '',
attrs: { attrs: {
'data-qa-selector': 'web_ide_button', 'data-qa-selector': 'web_ide_button',
'data-track-event': 'click_edit_ide',
// eslint-disable-next-line @gitlab/require-i18n-strings
'data-track-label': 'Web IDE',
}, },
...handleOptions, ...handleOptions,
}; };
...@@ -115,8 +169,14 @@ export default { ...@@ -115,8 +169,14 @@ export default {
</script> </script>
<template> <template>
<div> <div class="d-inline-block gl-ml-3">
<actions-button :actions="actions" :selected-key="selection" @select="select" /> <actions-button
:actions="actions"
:selected-key="selection"
:variant="isBlob ? 'info' : 'default'"
:category="isBlob ? 'primary' : 'secondary'"
@select="select"
/>
<local-storage-sync <local-storage-sync
storage-key="gl-web-ide-button-selected" storage-key="gl-web-ide-button-selected"
:value="selection" :value="selection"
......
...@@ -27,11 +27,19 @@ module BlobHelper ...@@ -27,11 +27,19 @@ module BlobHelper
end end
def ide_fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {}) def ide_fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {})
if current_user fork_path_for_current_user(project, ide_edit_path(project, ref, path))
project_forks_path(project, end
namespace_key: current_user&.namespace&.id,
continue: edit_blob_fork_params(ide_edit_path(project, ref, path))) def fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {})
fork_path_for_current_user(project, edit_blob_path(project, ref, path, options))
end end
def fork_path_for_current_user(project, path)
return unless current_user
project_forks_path(project,
namespace_key: current_user.namespace&.id,
continue: edit_blob_fork_params(path))
end end
def encode_ide_path(path) def encode_ide_path(path)
......
# frozen_string_literal: true # frozen_string_literal: true
module TreeHelper module TreeHelper
include BlobHelper
include WebIdeButtonHelper
FILE_LIMIT = 1_000 FILE_LIMIT = 1_000
# Sorts a repository's tree so that folders are before files and renders # Sorts a repository's tree so that folders are before files and renders
...@@ -199,38 +202,26 @@ module TreeHelper ...@@ -199,38 +202,26 @@ module TreeHelper
} }
end end
def web_ide_url_data(project) def web_ide_button_data(options = {})
can_push_code = current_user&.can?(:push_code, project) {
fork_path = current_user&.fork_of(project)&.full_path project_path: project_to_use.full_path,
ref: ActionDispatch::Journey::Router::Utils.escape_path(@ref),
if fork_path && !can_push_code is_fork: fork?,
{ path: fork_path, is_fork: true } needs_to_fork: needs_to_fork?,
else gitpod_enabled: !current_user.nil? && current_user.gitpod_enabled,
{ path: project.full_path, is_fork: false } is_blob: !options[:blob].nil?,
end
end
def vue_ide_link_data(project, ref) show_edit_button: show_edit_button?,
can_collaborate = can_collaborate_with_project?(project) show_web_ide_button: show_web_ide_button?,
can_create_mr_from_fork = can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project) show_gitpod_button: show_gitpod_button?,
show_web_ide_button = (can_collaborate || current_user&.already_forked?(project) || can_create_mr_from_fork)
{ web_ide_url: web_ide_url,
web_ide_url_data: web_ide_url_data(project), edit_url: edit_url,
needs_to_fork: !can_collaborate && !current_user&.already_forked?(project), gitpod_url: gitpod_url
show_web_ide_button: show_web_ide_button,
show_gitpod_button: show_web_ide_button && Gitlab::Gitpod.feature_and_settings_enabled?(project),
gitpod_url: full_gitpod_url(project, ref),
gitpod_enabled: current_user&.gitpod_enabled
} }
end end
def full_gitpod_url(project, ref)
return "" unless Gitlab::Gitpod.feature_and_settings_enabled?(project)
"#{Gitlab::CurrentSettings.gitpod_url}##{project_tree_url(project, tree_join(ref, @path || ''))}"
end
def directory_download_links(project, ref, archive_prefix) def directory_download_links(project, ref, archive_prefix)
Gitlab::Workhorse::ARCHIVE_FORMATS.map do |fmt| Gitlab::Workhorse::ARCHIVE_FORMATS.map do |fmt|
{ {
......
# frozen_string_literal: true
module WebIdeButtonHelper
def project_fork
current_user&.fork_of(@project)
end
def project_to_use
fork? ? project_fork : @project
end
def can_collaborate?
can_collaborate_with_project?(@project)
end
def can_create_mr_from_fork?
can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
end
def show_web_ide_button?
can_collaborate? || can_create_mr_from_fork?
end
def show_edit_button?
readable_blob? && show_web_ide_button?
end
def show_gitpod_button?
show_web_ide_button? && Gitlab::Gitpod.feature_and_settings_enabled?(@project)
end
def can_push_code?
current_user&.can?(:push_code, @project)
end
def fork?
!project_fork.nil? && !can_push_code?
end
def readable_blob?
!readable_blob({}, @path, @project, @ref).nil?
end
def needs_to_fork?
!can_collaborate? && !current_user&.already_forked?(@project)
end
def web_ide_url
ide_edit_path(project_to_use, @ref, @path || '')
end
def edit_url
readable_blob? ? edit_blob_path(@project, @ref, @path || '') : ''
end
def gitpod_url
return "" unless Gitlab::Gitpod.feature_and_settings_enabled?(@project)
"#{Gitlab::CurrentSettings.gitpod_url}##{project_tree_url(@project, tree_join(@ref, @path || ''))}"
end
end
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
.file-actions< .file-actions<
= render 'projects/blob/viewer_switcher', blob: blob unless blame = render 'projects/blob/viewer_switcher', blob: blob unless blame
- if Feature.enabled?(:consolidated_edit_button)
= render 'shared/web_ide_button', blob: blob
- else
= edit_blob_button(@project, @ref, @path, blob: blob) = edit_blob_button(@project, @ref, @path, blob: blob)
= ide_edit_button(@project, @ref, @path, blob: blob) = ide_edit_button(@project, @ref, @path, blob: blob)
.btn-group.ml-2{ role: "group" }> .btn-group.ml-2{ role: "group" }>
......
- can_collaborate = can_collaborate_with_project?(@project)
- can_create_mr_from_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
- can_visit_ide = can_collaborate || current_user&.already_forked?(@project)
.tree-ref-container .tree-ref-container
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true = render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
...@@ -14,13 +10,7 @@ ...@@ -14,13 +10,7 @@
#js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } } #js-tree-history-link.d-inline-block{ data: { history_link: project_commits_path(@project, @ref) } }
= render 'projects/find_file_link' = render 'projects/find_file_link'
= render 'shared/web_ide_button', blob: nil
- if can_visit_ide || can_create_mr_from_fork
#js-tree-web-ide-link.d-inline-block{ data: { options: vue_ide_link_data(@project, @ref).to_json } }
- if !can_visit_ide
= render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path)
- unless current_user&.gitpod_enabled
= render 'shared/gitpod/enable_gitpod_modal'
- if show_xcode_link?(@project) - if show_xcode_link?(@project)
.project-action-button.project-xcode.inline< .project-action-button.project-xcode.inline<
......
#modal-confirm-fork.modal{ data: { qa_selector: 'confirm_fork_modal' } } .modal{ data: { qa_selector: 'confirm_fork_modal'}, id: "modal-confirm-fork-#{type}" }
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
......
- type = blob ? 'blob' : 'tree'
.d-inline-block{ data: { options: web_ide_button_data(blob: blob).to_json }, id: "js-#{type}-web-ide-link" }
- if show_edit_button?
= render 'shared/confirm_fork_modal', fork_path: fork_and_edit_path(@project, @ref, @path), type: 'edit'
- if show_web_ide_button?
= render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path), type: 'webide'
- if show_gitpod_button?
= render 'shared/gitpod/enable_gitpod_modal'
---
name: consolidated_edit_button
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44311
rollout_issue_url:
group: group::editor
type: development
default_enabled: false
...@@ -9545,6 +9545,9 @@ msgstr "" ...@@ -9545,6 +9545,9 @@ msgstr ""
msgid "Edit stage" msgid "Edit stage"
msgstr "" msgstr ""
msgid "Edit this file only."
msgstr ""
msgid "Edit this release" msgid "Edit this release"
msgstr "" msgstr ""
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Editing file blob', :js do RSpec.describe 'Editing file blob', :js do
include TreeHelper include TreeHelper
include BlobSpecHelpers
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
...@@ -21,13 +22,17 @@ RSpec.describe 'Editing file blob', :js do ...@@ -21,13 +22,17 @@ RSpec.describe 'Editing file blob', :js do
end end
def edit_and_commit(commit_changes: true, is_diff: false) def edit_and_commit(commit_changes: true, is_diff: false)
set_default_button('edit')
refresh
wait_for_requests wait_for_requests
if is_diff if is_diff
first('.js-diff-more-actions').click first('.js-diff-more-actions').click
click_link('Edit in single-file editor')
else
click_link('Edit')
end end
first('.js-edit-blob').click
fill_editor(content: 'class NextFeature\\nend\\n') fill_editor(content: 'class NextFeature\\nend\\n')
if commit_changes if commit_changes
......
...@@ -66,10 +66,30 @@ RSpec.describe 'Projects > Files > User browses LFS files' do ...@@ -66,10 +66,30 @@ RSpec.describe 'Projects > Files > User browses LFS files' do
expect(page).to have_content('History') expect(page).to have_content('History')
expect(page).to have_content('Permalink') expect(page).to have_content('Permalink')
expect(page).to have_content('Replace') expect(page).to have_content('Replace')
expect(page).to have_link('Download')
expect(page).not_to have_content('Annotate') expect(page).not_to have_content('Annotate')
expect(page).not_to have_content('Blame') expect(page).not_to have_content('Blame')
expect(page).not_to have_content('Edit')
expect(page).to have_link('Download') expect(page).not_to have_selector(:link_or_button, text: /^Edit$/)
expect(page).to have_selector(:link_or_button, 'Edit in Web IDE')
end
end
context 'when feature flag :consolidated_edit_button is off' do
before do
stub_feature_flags(consolidated_edit_button: false)
click_link('files')
click_link('lfs')
click_link('lfs_object.iso')
end
it 'does not show single file edit link' do
page.within('.content') do
expect(page).to have_selector(:link_or_button, 'Web IDE')
expect(page).not_to have_selector(:link_or_button, 'Edit')
end
end end
end end
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Files > User creates files', :js do RSpec.describe 'Projects > Files > User creates files', :js do
include BlobSpecHelpers
let(:fork_message) do let(:fork_message) do
"You're not allowed to make changes to this project directly. "\ "You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request." "A fork of this project has been created that you can make changes in, so you can submit a merge request."
...@@ -103,6 +105,8 @@ RSpec.describe 'Projects > Files > User creates files', :js do ...@@ -103,6 +105,8 @@ RSpec.describe 'Projects > Files > User creates files', :js do
end end
it 'creates and commit a new file with new lines at the end of file' do it 'creates and commit a new file with new lines at the end of file' do
set_default_button('edit')
find('#editor') find('#editor')
execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")') execute_script('monaco.editor.getModels()[0].setValue("Sample\n\n\n")')
fill_in(:file_name, with: 'not_a_file.md') fill_in(:file_name, with: 'not_a_file.md')
...@@ -113,7 +117,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do ...@@ -113,7 +117,7 @@ RSpec.describe 'Projects > Files > User creates files', :js do
expect(current_path).to eq(new_file_path) expect(current_path).to eq(new_file_path)
find('.js-edit-blob').click click_link('Edit')
find('#editor') find('#editor')
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n") expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq("Sample\n\n\n")
......
...@@ -4,6 +4,8 @@ require 'spec_helper' ...@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe 'Projects > Files > User edits files', :js do RSpec.describe 'Projects > Files > User edits files', :js do
include ProjectForksHelper include ProjectForksHelper
include BlobSpecHelpers
let(:project) { create(:project, :repository, name: 'Shop') } let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
...@@ -14,6 +16,10 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -14,6 +16,10 @@ RSpec.describe 'Projects > Files > User edits files', :js do
sign_in(user) sign_in(user)
end end
after do
unset_default_button
end
shared_examples 'unavailable for an archived project' do shared_examples 'unavailable for an archived project' do
it 'does not show the edit link for an archived project', :js do it 'does not show the edit link for an archived project', :js do
project.update!(archived: true) project.update!(archived: true)
...@@ -39,14 +45,15 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -39,14 +45,15 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'inserts a content of a file' do it 'inserts a content of a file' do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca') expect(editor_value).to eq('*.rbca')
end end
it 'does not show the edit link if a file is binary' do it 'does not show the edit link if a file is binary' do
...@@ -60,12 +67,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -60,12 +67,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'commits an edited file' do it 'commits an edited file' do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes') click_button('Commit changes')
...@@ -77,13 +85,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -77,13 +85,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'commits an edited file to a new branch' do it 'commits an edited file to a new branch' do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true) fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes') click_button('Commit changes')
...@@ -96,12 +105,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -96,12 +105,13 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'shows the diff of an edited file' do it 'shows the diff of an edited file' do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
click_link('Preview changes') click_link('Preview changes')
expect(page).to have_css('.line_holder.new') expect(page).to have_css('.line_holder.new')
...@@ -118,8 +128,8 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -118,8 +128,8 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
def expect_fork_prompt def expect_fork_prompt
expect(page).to have_link('Fork') expect(page).to have_selector(:link_or_button, 'Fork')
expect(page).to have_button('Cancel') expect(page).to have_selector(:link_or_button, 'Cancel')
expect(page).to have_content( expect(page).to have_content(
"You're not allowed to edit files in this project directly. "\ "You're not allowed to edit files in this project directly. "\
"Please fork this project, make your changes there, and submit a merge request." "Please fork this project, make your changes there, and submit a merge request."
...@@ -134,30 +144,32 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -134,30 +144,32 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
click_button('Edit') click_link_or_button('Edit')
expect_fork_prompt expect_fork_prompt
click_link('Fork') click_link_or_button('Fork project')
expect_fork_status expect_fork_status
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
expect(evaluate_script('monaco.editor.getModels()[0].getValue()')).to eq('*.rbca') expect(editor_value).to eq('*.rbca')
end end
it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do
set_default_button('webide')
click_link('.gitignore') click_link('.gitignore')
click_button('Web IDE') click_link_or_button('Web IDE')
expect_fork_prompt expect_fork_prompt
click_link('Fork') click_link_or_button('Fork project')
expect_fork_status expect_fork_status
...@@ -166,17 +178,126 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -166,17 +178,126 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
expect_fork_prompt expect_fork_prompt
click_link_or_button('Fork project')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork))
wait_for_requests
expect(page).to have_content('New commit message')
end
context 'when the user already had a fork of the project', :js do
let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
before do
visit(project2_tree_path_root_ref)
wait_for_requests
end
it 'links to the forked project for editing', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect(page).not_to have_link('Fork project')
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
fork = user.fork_of(project2)
click_link('Fork') expect(current_path).to eq(project_new_merge_request_path(fork))
wait_for_requests
expect(page).to have_content('Another commit')
expect(page).to have_content("From #{forked_project.full_path}")
expect(page).to have_content("into #{project2.full_path}")
end
it_behaves_like 'unavailable for an archived project' do
let(:project) { project2 }
end
end
context 'when feature flag :consolidated_edit_button is off' do
before do
stub_feature_flags(consolidated_edit_button: false)
end
context 'when an user does not have write access', :js do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
wait_for_requests
end
it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect_fork_prompt
click_link_or_button('Fork')
expect_fork_status
find('.file-editor', match: :first) find('.file-editor', match: :first)
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
expect(editor_value).to eq('*.rbca')
end
it 'opens the Web IDE in a forked project', :sidekiq_might_not_need_inline do
set_default_button('webide')
click_link('.gitignore')
click_link_or_button('Web IDE')
expect_fork_prompt
click_link_or_button('Fork')
expect_fork_status
expect(page).to have_css('.ide-sidebar-project-title', text: "#{project2.name} #{user.namespace.full_path}/#{project2.path}")
expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore')
end
it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect_fork_prompt
click_link_or_button('Fork')
expect_fork_status
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes') click_button('Commit changes')
...@@ -198,14 +319,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -198,14 +319,14 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
it 'links to the forked project for editing', :sidekiq_might_not_need_inline do it 'links to the forked project for editing', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore') click_link('.gitignore')
find('.js-edit-blob').click click_link_or_button('Edit')
expect(page).not_to have_link('Fork') expect(page).not_to have_link('Fork')
expect(page).not_to have_button('Cancel')
find('#editor') find('#editor')
execute_script("monaco.editor.getModels()[0].setValue('*.rbca')") set_editor_value('*.rbca')
fill_in(:commit_message, with: 'Another commit', visible: true) fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes') click_button('Commit changes')
...@@ -225,4 +346,6 @@ RSpec.describe 'Projects > Files > User edits files', :js do ...@@ -225,4 +346,6 @@ RSpec.describe 'Projects > Files > User edits files', :js do
end end
end end
end end
end
end
end end
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlLink } from '@gitlab/ui'; import { GlDropdown, GlButton } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ActionsButton from '~/vue_shared/components/actions_button.vue'; import ActionsButton from '~/vue_shared/components/actions_button.vue';
...@@ -9,7 +9,12 @@ const TEST_ACTION = { ...@@ -9,7 +9,12 @@ const TEST_ACTION = {
secondaryText: 'Lorem ipsum.', secondaryText: 'Lorem ipsum.',
tooltip: '', tooltip: '',
href: '/sample', href: '/sample',
attrs: { 'data-test': '123' }, attrs: {
'data-test': '123',
category: 'secondary',
href: '/sample',
variant: 'default',
},
}; };
const TEST_ACTION_2 = { const TEST_ACTION_2 = {
key: 'action2', key: 'action2',
...@@ -40,8 +45,8 @@ describe('Actions button component', () => { ...@@ -40,8 +45,8 @@ describe('Actions button component', () => {
return directiveBinding.value; return directiveBinding.value;
}; };
const findLink = () => wrapper.find(GlLink); const findButton = () => wrapper.find(GlButton);
const findLinkTooltip = () => getTooltip(findLink()); const findButtonTooltip = () => getTooltip(findButton());
const findDropdown = () => wrapper.find(GlDropdown); const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownTooltip = () => getTooltip(findDropdown()); const findDropdownTooltip = () => getTooltip(findDropdown());
const parseDropdownItems = () => const parseDropdownItems = () =>
...@@ -63,7 +68,7 @@ describe('Actions button component', () => { ...@@ -63,7 +68,7 @@ describe('Actions button component', () => {
}; };
}); });
const clickOn = (child, evt = new Event('click')) => child.vm.$emit('click', evt); const clickOn = (child, evt = new Event('click')) => child.vm.$emit('click', evt);
const clickLink = (...args) => clickOn(findLink(), ...args); const clickLink = (...args) => clickOn(findButton(), ...args);
const clickDropdown = (...args) => clickOn(findDropdown(), ...args); const clickDropdown = (...args) => clickOn(findDropdown(), ...args);
describe('with 1 action', () => { describe('with 1 action', () => {
...@@ -76,22 +81,19 @@ describe('Actions button component', () => { ...@@ -76,22 +81,19 @@ describe('Actions button component', () => {
}); });
it('should render single button', () => { it('should render single button', () => {
const link = findLink(); expect(findButton().attributes()).toMatchObject({
expect(link.attributes()).toEqual({
class: expect.any(String),
href: TEST_ACTION.href, href: TEST_ACTION.href,
...TEST_ACTION.attrs, ...TEST_ACTION.attrs,
}); });
expect(link.text()).toBe(TEST_ACTION.text); expect(findButton().text()).toBe(TEST_ACTION.text);
}); });
it('should have tooltip', () => { it('should have tooltip', () => {
expect(findLinkTooltip()).toBe(TEST_ACTION.tooltip); expect(findButtonTooltip()).toBe(TEST_ACTION.tooltip);
}); });
it('should have attrs', () => { it('should have attrs', () => {
expect(findLink().attributes()).toMatchObject(TEST_ACTION.attrs); expect(findButton().attributes()).toMatchObject(TEST_ACTION.attrs);
}); });
it('can click', () => { it('can click', () => {
...@@ -103,7 +105,7 @@ describe('Actions button component', () => { ...@@ -103,7 +105,7 @@ describe('Actions button component', () => {
it('should have tooltip', () => { it('should have tooltip', () => {
createComponent({ actions: [{ ...TEST_ACTION, tooltip: TEST_TOOLTIP }] }); createComponent({ actions: [{ ...TEST_ACTION, tooltip: TEST_TOOLTIP }] });
expect(findLinkTooltip()).toBe(TEST_TOOLTIP); expect(findButtonTooltip()).toBe(TEST_TOOLTIP);
}); });
}); });
......
...@@ -3,9 +3,27 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; ...@@ -3,9 +3,27 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue'; import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue'; import ActionsButton from '~/vue_shared/components/actions_button.vue';
const TEST_EDIT_URL = '/gitlab-test/test/-/edit/master/';
const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/master/-/'; const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/master/-/';
const TEST_GITPOD_URL = 'https://gitpod.test/'; const TEST_GITPOD_URL = 'https://gitpod.test/';
const ACTION_EDIT = {
href: TEST_EDIT_URL,
key: 'edit',
text: 'Edit',
secondaryText: 'Edit this file only.',
tooltip: '',
attrs: {
'data-qa-selector': 'edit_button',
'data-track-event': 'click_edit',
'data-track-label': 'Edit',
},
};
const ACTION_EDIT_CONFIRM_FORK = {
...ACTION_EDIT,
href: '#modal-confirm-fork-edit',
handle: expect.any(Function),
};
const ACTION_WEB_IDE = { const ACTION_WEB_IDE = {
href: TEST_WEB_IDE_URL, href: TEST_WEB_IDE_URL,
key: 'webide', key: 'webide',
...@@ -14,13 +32,16 @@ const ACTION_WEB_IDE = { ...@@ -14,13 +32,16 @@ const ACTION_WEB_IDE = {
text: 'Web IDE', text: 'Web IDE',
attrs: { attrs: {
'data-qa-selector': 'web_ide_button', 'data-qa-selector': 'web_ide_button',
'data-track-event': 'click_edit_ide',
'data-track-label': 'Web IDE',
}, },
}; };
const ACTION_WEB_IDE_FORK = { const ACTION_WEB_IDE_CONFIRM_FORK = {
...ACTION_WEB_IDE, ...ACTION_WEB_IDE,
href: '#modal-confirm-fork', href: '#modal-confirm-fork-webide',
handle: expect.any(Function), handle: expect.any(Function),
}; };
const ACTION_WEB_IDE_EDIT_FORK = { ...ACTION_WEB_IDE, text: 'Edit fork in Web IDE' };
const ACTION_GITPOD = { const ACTION_GITPOD = {
href: TEST_GITPOD_URL, href: TEST_GITPOD_URL,
key: 'gitpod', key: 'gitpod',
...@@ -43,6 +64,7 @@ describe('Web IDE link component', () => { ...@@ -43,6 +64,7 @@ describe('Web IDE link component', () => {
function createComponent(props) { function createComponent(props) {
wrapper = shallowMount(WebIdeLink, { wrapper = shallowMount(WebIdeLink, {
propsData: { propsData: {
editUrl: TEST_EDIT_URL,
webIdeUrl: TEST_WEB_IDE_URL, webIdeUrl: TEST_WEB_IDE_URL,
gitpodUrl: TEST_GITPOD_URL, gitpodUrl: TEST_GITPOD_URL,
...props, ...props,
...@@ -57,15 +79,36 @@ describe('Web IDE link component', () => { ...@@ -57,15 +79,36 @@ describe('Web IDE link component', () => {
const findActionsButton = () => wrapper.find(ActionsButton); const findActionsButton = () => wrapper.find(ActionsButton);
const findLocalStorageSync = () => wrapper.find(LocalStorageSync); const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
it.each` it.each([
props | expectedActions {
${{}} | ${[ACTION_WEB_IDE]} props: {},
${{ webIdeIsFork: true }} | ${[{ ...ACTION_WEB_IDE, text: 'Edit fork in Web IDE' }]} expectedActions: [ACTION_WEB_IDE, ACTION_EDIT],
${{ needsToFork: true }} | ${[ACTION_WEB_IDE_FORK]} },
${{ showWebIdeButton: false, showGitpodButton: true, gitpodEnabled: true }} | ${[ACTION_GITPOD]} {
${{ showWebIdeButton: false, showGitpodButton: true, gitpodEnabled: false }} | ${[ACTION_GITPOD_ENABLE]} props: { isFork: true },
${{ showGitpodButton: true, gitpodEnabled: false }} | ${[ACTION_WEB_IDE, ACTION_GITPOD_ENABLE]} expectedActions: [ACTION_WEB_IDE_EDIT_FORK, ACTION_EDIT],
`('renders actions with props=$props', ({ props, expectedActions }) => { },
{
props: { needsToFork: true },
expectedActions: [ACTION_WEB_IDE_CONFIRM_FORK, ACTION_EDIT_CONFIRM_FORK],
},
{
props: { showWebIdeButton: false, showGitpodButton: true, gitpodEnabled: true },
expectedActions: [ACTION_EDIT, ACTION_GITPOD],
},
{
props: { showWebIdeButton: false, showGitpodButton: true, gitpodEnabled: false },
expectedActions: [ACTION_EDIT, ACTION_GITPOD_ENABLE],
},
{
props: { showGitpodButton: true, gitpodEnabled: false },
expectedActions: [ACTION_WEB_IDE, ACTION_EDIT, ACTION_GITPOD_ENABLE],
},
{
props: { showEditButton: false },
expectedActions: [ACTION_WEB_IDE],
},
])('renders actions with appropriately for given props', ({ props, expectedActions }) => {
createComponent(props); createComponent(props);
expect(findActionsButton().props('actions')).toEqual(expectedActions); expect(findActionsButton().props('actions')).toEqual(expectedActions);
...@@ -73,7 +116,12 @@ describe('Web IDE link component', () => { ...@@ -73,7 +116,12 @@ describe('Web IDE link component', () => {
describe('with multiple actions', () => { describe('with multiple actions', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ showWebIdeButton: true, showGitpodButton: true, gitpodEnabled: true }); createComponent({
showEditButton: false,
showWebIdeButton: true,
showGitpodButton: true,
gitpodEnabled: true,
});
}); });
it('selected Web IDE by default', () => { it('selected Web IDE by default', () => {
......
...@@ -445,13 +445,14 @@ RSpec.describe BlobHelper do ...@@ -445,13 +445,14 @@ RSpec.describe BlobHelper do
end end
describe '#ide_fork_and_edit_path' do describe '#ide_fork_and_edit_path' do
let(:project) { create(:project) } let_it_be(:project) { create(:project) }
let(:current_user) { create(:user) } let_it_be(:user) { create(:user) }
let(:can_push_code) { true }
let(:current_user) { user }
before do before do
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
allow(helper).to receive(:can?).and_return(can_push_code) allow(helper).to receive(:can?).and_return(true)
end end
it 'returns path to fork the repo with a redirect param to the full IDE path' do it 'returns path to fork the repo with a redirect param to the full IDE path' do
...@@ -472,6 +473,35 @@ RSpec.describe BlobHelper do ...@@ -472,6 +473,35 @@ RSpec.describe BlobHelper do
end end
end end
describe '#fork_and_edit_path' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:current_user) { user }
before do
allow(helper).to receive(:current_user).and_return(current_user)
allow(helper).to receive(:can?).and_return(true)
end
it 'returns path to fork the repo with a redirect param to the full edit path' do
uri = URI(helper.fork_and_edit_path(project, "master", ""))
params = CGI.unescape(uri.query)
expect(uri.path).to eq("/#{project.namespace.path}/#{project.path}/-/forks")
expect(params).to include("continue[to]=/#{project.namespace.path}/#{project.path}/-/edit/master/")
expect(params).to include("namespace_key=#{current_user.namespace.id}")
end
context 'when user is not logged in' do
let(:current_user) { nil }
it 'returns nil' do
expect(helper.ide_fork_and_edit_path(project, "master", "")).to be_nil
end
end
end
describe '#editing_ci_config?' do describe '#editing_ci_config?' do
let(:project) { build(:project) } let(:project) { build(:project) }
......
...@@ -167,31 +167,60 @@ RSpec.describe TreeHelper do ...@@ -167,31 +167,60 @@ RSpec.describe TreeHelper do
end end
end end
describe '#vue_ide_link_data' do describe '#web_ide_button_data' do
let(:blob) { project.repository.blob_at('refs/heads/master', @path) }
before do before do
@path = ''
@project = project
@ref = sha
allow(helper).to receive(:current_user).and_return(nil) allow(helper).to receive(:current_user).and_return(nil)
allow(helper).to receive(:can_collaborate_with_project?).and_return(true) allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
allow(helper).to receive(:can?).and_return(true) allow(helper).to receive(:can?).and_return(true)
end end
subject { helper.vue_ide_link_data(project, sha) } subject { helper.web_ide_button_data(blob: blob) }
it 'returns a list of attributes related to the project' do it 'returns a list of attributes related to the project' do
expect(subject).to include( expect(subject).to include(
web_ide_url_data: { path: project.full_path, is_fork: false }, project_path: project.full_path,
ref: sha,
is_fork: false,
needs_to_fork: false, needs_to_fork: false,
gitpod_enabled: false,
is_blob: false,
show_edit_button: false,
show_web_ide_button: true, show_web_ide_button: true,
show_gitpod_button: false, show_gitpod_button: false,
gitpod_url: "",
gitpod_enabled: nil edit_url: '',
web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}",
gitpod_url: ''
)
end
context 'a blob is passed' do
before do
@path = 'README.md'
end
it 'returns edit url and webide url for the blob' do
expect(subject).to include(
show_edit_button: true,
edit_url: "/#{project.full_path}/-/edit/#{sha}/#{@path}",
web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}/-/#{@path}"
) )
end end
end
context 'user does not have write access but a personal fork exists' do context 'user does not have write access but a personal fork exists' do
include ProjectForksHelper include ProjectForksHelper
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let!(:forked_project) { create(:project, :repository, namespace: user.namespace) } let(:forked_project) { create(:project, :repository, namespace: user.namespace) }
before do before do
project.add_guest(user) project.add_guest(user)
...@@ -200,9 +229,49 @@ RSpec.describe TreeHelper do ...@@ -200,9 +229,49 @@ RSpec.describe TreeHelper do
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
end end
it 'includes web_ide_url_data: forked_project.full_path' do it 'includes forked project path as project_path' do
expect(subject).to include( expect(subject).to include(
web_ide_url_data: { path: forked_project.full_path, is_fork: true } project_path: forked_project.full_path,
is_fork: true,
needs_to_fork: false,
show_edit_button: false,
web_ide_url: "/-/ide/project/#{forked_project.full_path}/edit/#{sha}"
)
end
context 'a blob is passed' do
before do
@path = 'README.md'
end
it 'returns edit url and web ide for the blob in the fork' do
expect(subject).to include(
is_blob: true,
show_edit_button: true,
# edit urls are automatically redirected to the fork
edit_url: "/#{project.full_path}/-/edit/#{sha}/#{@path}",
web_ide_url: "/-/ide/project/#{forked_project.full_path}/edit/#{sha}/-/#{@path}"
)
end
end
end
context 'for archived project' do
before do
allow(helper).to receive(:can_collaborate_with_project?).and_return(false)
allow(helper).to receive(:can?).and_return(false)
project.update!(archived: true)
@path = 'README.md'
end
it 'does not show any buttons' do
expect(subject).to include(
is_blob: true,
show_edit_button: false,
show_web_ide_button: false,
show_gitpod_button: false
) )
end end
end end
...@@ -216,12 +285,33 @@ RSpec.describe TreeHelper do ...@@ -216,12 +285,33 @@ RSpec.describe TreeHelper do
allow(helper).to receive(:current_user).and_return(user) allow(helper).to receive(:current_user).and_return(user)
end end
it 'includes web_ide_url_data: project.full_path' do it 'includes original project path as project_path' do
expect(subject).to include( expect(subject).to include(
web_ide_url_data: { path: project.full_path, is_fork: false } project_path: project.full_path,
is_fork: false,
needs_to_fork: false,
show_edit_button: false,
web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}"
)
end
context 'a blob is passed' do
before do
@path = 'README.md'
end
it 'returns edit url and web ide for the blob in the fork' do
expect(subject).to include(
is_blob: true,
show_edit_button: true,
edit_url: "/#{project.full_path}/-/edit/#{sha}/#{@path}",
web_ide_url: "/-/ide/project/#{project.full_path}/edit/#{sha}/-/#{@path}"
) )
end end
end end
end
context 'gitpod feature is enabled' do context 'gitpod feature is enabled' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
......
# frozen_string_literal: true
# These helpers help you interact within the blobs page and blobs edit page (Single file editor).
module BlobSpecHelpers
include ActionView::Helpers::JavaScriptHelper
def set_default_button(type)
evaluate_script("localStorage.setItem('gl-web-ide-button-selected', '#{type}')")
end
def unset_default_button
set_default_button('')
end
def editor_value
evaluate_script('monaco.editor.getModels()[0].getValue()')
end
def set_editor_value(value)
execute_script("monaco.editor.getModels()[0].setValue('#{value}')")
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment