Commit 080aded4 authored by Anastasia McDonald's avatar Anastasia McDonald

Merge branch 'blob-refactor-specs' into 'master'

Blob refactor: Update/fix blob refactor specs

See merge request gitlab-org/gitlab!76427
parents 768b03ba 9fbefad9
import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/;
const lineNumberRe = /^(L|LC)[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = getLocationHash();
......
......@@ -81,7 +81,7 @@ export default {
</blob-filepath>
</div>
<div class="gl-display-none gl-sm-display-flex file-actions">
<div class="gl-sm-display-flex file-actions">
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" />
<slot name="actions"></slot>
......
......@@ -53,6 +53,8 @@ export default {
icon="code"
category="primary"
variant="default"
class="js-blob-viewer-switch-btn"
data-viewer="simple"
@click="switchToViewer($options.SIMPLE_BLOB_VIEWER)"
/>
<gl-button
......@@ -63,6 +65,8 @@ export default {
icon="document"
category="primary"
variant="default"
class="js-blob-viewer-switch-btn"
data-viewer="rich"
@click="switchToViewer($options.RICH_BLOB_VIEWER)"
/>
</gl-button-group>
......
......@@ -41,6 +41,7 @@ if (viewBlobEl) {
});
initAuxiliaryViewer();
initBlob();
} else {
new BlobViewer(); // eslint-disable-line no-new
initBlob();
......
......@@ -108,6 +108,7 @@ export default {
externalStorage: '',
canModifyBlob: false,
canCurrentUserPushToBranch: false,
archived: false,
rawPath: '',
externalStorageUrl: '',
replacePath: '',
......@@ -167,7 +168,7 @@ export default {
return pushCode && downloadCode;
},
pathLockedByUser() {
const pathLock = this.project.pathLocks.nodes.find((node) => node.path === this.path);
const pathLock = this.project?.pathLocks?.nodes.find((node) => node.path === this.path);
return pathLock ? pathLock.user : null;
},
......@@ -250,6 +251,7 @@ export default {
>
<template #actions>
<blob-edit
v-if="!blobInfo.archived"
:show-edit-button="!isBinaryFileType"
:edit-path="blobInfo.editBlobPath"
:web-ide-path="blobInfo.ideEditPath"
......@@ -269,7 +271,7 @@ export default {
</gl-button>
<blob-button-group
v-if="isLoggedIn"
v-if="isLoggedIn && !blobInfo.archived"
:path="path"
:name="blobInfo.name"
:replace-path="blobInfo.replacePath"
......@@ -292,6 +294,7 @@ export default {
/>
<blob-content
v-if="!blobViewer"
class="js-syntax-highlight"
:rich-viewer="legacyRichViewer"
:blob="blobInfo"
:content="legacySimpleViewer"
......
......@@ -55,6 +55,8 @@ export default {
blamePath: null,
historyPath: null,
permalinkPath: null,
storedExternally: null,
externalStorage: null,
},
],
},
......@@ -72,6 +74,9 @@ export default {
blobInfo() {
return this.project?.repository?.blobs?.nodes[0] || {};
},
showBlameButton() {
return !this.blobInfo.storedExternally && this.blobInfo.externalStorage !== 'lfs';
},
},
watch: {
showBlobControls(shouldShow) {
......@@ -89,7 +94,12 @@ export default {
<gl-button data-testid="find" :href="blobInfo.findFilePath" :class="$options.buttonClassList">
{{ $options.i18n.findFile }}
</gl-button>
<gl-button data-testid="blame" :href="blobInfo.blamePath" :class="$options.buttonClassList">
<gl-button
v-if="showBlameButton"
data-testid="blame"
:href="blobInfo.blamePath"
:class="$options.buttonClassList"
>
{{ $options.i18n.blame }}
</gl-button>
......
......@@ -50,6 +50,7 @@ export default {
:web-ide-url="webIdePath"
:needs-to-fork="needsToFork"
:is-blob="true"
disable-fork-modal
@edit="onEdit"
/>
<div v-else>
......
......@@ -9,6 +9,8 @@ query getBlobControls($projectPath: ID!, $filePath: String!, $ref: String!) {
blamePath
historyPath
permalinkPath
storedExternally
externalStorage
}
}
}
......
#import "ee_else_ce/repository/queries/path_locks.fragment.graphql"
query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
project(fullPath: $projectPath) {
id
userPermissions {
pushCode
downloadCode
createMergeRequestIn
forkProject
}
pathLocks {
nodes {
id
path
user {
id
username
}
}
}
...ProjectPathLocksFragment
repository {
empty
blobs(paths: [$filePath], ref: $ref) {
......@@ -35,6 +27,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
ideForkAndEditPath
canModifyBlob
canCurrentUserPushToBranch
archived
storedExternally
externalStorage
rawPath
......
fragment ProjectPathLocksFragment on Project {
id
}
......@@ -111,7 +111,7 @@ export default {
};
</script>
<template>
<div class="file-content code" :class="$options.userColorScheme">
<div class="file-content code js-syntax-highlight" :class="$options.userColorScheme">
<line-numbers :lines="lineNumbers" @select-line="selectLine" />
<pre class="code"><code v-safe-html="highlightedContent"></code>
</pre>
......
......@@ -98,6 +98,11 @@ export default {
required: false,
default: '',
},
disableForkModal: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -118,7 +123,11 @@ export default {
? {
href: '#modal-confirm-fork-edit',
handle: () => {
this.$emit('edit', 'simple');
if (this.disableForkModal) {
this.$emit('edit', 'simple');
return;
}
this.showJQueryModal('#modal-confirm-fork-edit');
},
}
......@@ -157,7 +166,11 @@ export default {
? {
href: '#modal-confirm-fork-webide',
handle: () => {
this.$emit('edit', 'ide');
if (this.disableForkModal) {
this.$emit('edit', 'ide');
return;
}
this.showJQueryModal('#modal-confirm-fork-webide');
},
}
......
......@@ -21,6 +21,8 @@ class Projects::TreeController < Projects::ApplicationController
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:highlight_js, @project, default_enabled: :yaml)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
end
feature_category :source_code_management
......
......@@ -40,6 +40,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
end
layout :determine_layout
......
......@@ -114,6 +114,9 @@ module Types
field :can_current_user_push_to_branch, GraphQL::Types::Boolean, null: true, method: :can_current_user_push_to_branch?,
description: 'Whether the current user can push to the branch.'
field :archived, GraphQL::Types::Boolean, null: true, method: :archived?,
description: 'Whether the current project is archived.'
def raw_text_blob
object.data unless object.binary?
end
......
......@@ -102,6 +102,10 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
user_access(project).can_push_to_branch?(blob.commit_id)
end
def archived?
project.archived
end
def ide_edit_path
super(project, blob.commit_id, blob.path)
end
......
......@@ -14513,6 +14513,7 @@ Returns [`Tree`](#tree).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="repositoryblobarchived"></a>`archived` | [`Boolean`](#boolean) | Whether the current project is archived. |
| <a id="repositoryblobblamepath"></a>`blamePath` | [`String`](#string) | Web path to blob blame page. |
| <a id="repositoryblobcancurrentuserpushtobranch"></a>`canCurrentUserPushToBranch` | [`Boolean`](#boolean) | Whether the current user can push to the branch. |
| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
fragment ProjectPathLocksFragment on Project {
id
pathLocks {
nodes {
id
path
user {
id
username
}
}
}
}
......@@ -8,7 +8,6 @@ RSpec.describe 'Path Locks', :js do
let(:tree_path) { project_tree_path(project, project.repository.root_ref) }
before do
allow(project).to receive(:feature_available?).with(:file_locks) { true }
stub_feature_flags(bootstrap_confirmation_modals: false)
project.add_maintainer(user)
......@@ -37,10 +36,16 @@ RSpec.describe 'Path Locks', :js do
end
within '.file-actions' do
accept_confirm(text: 'Are you sure you want to lock VERSION?') { click_link "Lock" }
click_button "Lock"
end
page.within '.modal' do
expect(page).to have_css('.modal-body', text: 'Are you sure you want to lock VERSION?')
expect(page).to have_link('Unlock')
click_button "Okay"
end
expect(page).to have_button('Unlock')
end
it 'unlocking files' do
......@@ -49,16 +54,24 @@ RSpec.describe 'Path Locks', :js do
end
within '.file-actions' do
accept_confirm(text: 'Are you sure you want to lock VERSION?') { click_link "Lock" }
click_button "Lock"
end
expect(page).to have_link('Unlock')
page.within '.modal' do
click_button "Okay"
end
within '.file-actions' do
accept_confirm(text: 'Are you sure you want to unlock VERSION?') { click_link "Unlock" }
click_button "Unlock"
end
page.within '.modal' do
expect(page).to have_css('.modal-body', text: 'Are you sure you want to unlock VERSION?')
expect(page).to have_link('Lock')
click_button "Okay"
end
expect(page).to have_button('Lock')
end
it 'managing of lock list' do
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import {
simpleViewerMock,
projectMock,
userPermissionsMock,
propsMock,
refMock,
} from 'jest/repository/mock_data';
jest.mock('~/lib/utils/common_utils');
let wrapper;
let mockResolver;
Vue.use(VueApollo);
const createComponent = async (mockData = {}) => {
const {
blob = simpleViewerMock,
empty = projectMock.repository.empty,
pushCode = userPermissionsMock.pushCode,
forkProject = userPermissionsMock.forkProject,
downloadCode = userPermissionsMock.downloadCode,
createMergeRequestIn = userPermissionsMock.createMergeRequestIn,
isBinary,
inject = {},
path = propsMock.projectPath,
} = mockData;
const project = {
...projectMock,
userPermissions: {
pushCode,
forkProject,
downloadCode,
createMergeRequestIn,
},
repository: { empty, blobs: { nodes: [blob] } },
};
mockResolver = jest.fn().mockResolvedValue({
data: { isBinary, project },
});
const fakeApollo = createMockApollo([[blobInfoQuery, mockResolver]]);
wrapper = mountExtended(BlobContentViewer, {
apolloProvider: fakeApollo,
propsData: {
...propsMock,
path,
},
mixins: [{ data: () => ({ ref: refMock }) }],
provide: { ...inject },
});
await waitForPromises();
};
describe('Blob content viewer component', () => {
const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
beforeEach(() => {
gon.features = { highlightJs: true };
isLoggedIn.mockReturnValue(true);
});
afterEach(() => {
wrapper.destroy();
});
describe('BlobHeader action slot', () => {
describe('BlobButtonGroup', () => {
const {
repository: { empty },
} = projectMock;
afterEach(() => {
delete gon.current_username;
});
it.each`
canPushCode | canDownloadCode | username | canLock
${true} | ${true} | ${'root'} | ${true}
${false} | ${true} | ${'root'} | ${false}
${true} | ${false} | ${'root'} | ${false}
${true} | ${true} | ${'peter'} | ${false}
`(
'passes the correct lock states',
async ({ canPushCode, canDownloadCode, username, canLock }) => {
gon.current_username = username;
await createComponent({
pushCode: canPushCode,
downloadCode: canDownloadCode,
empty,
path: 'locked_file.js',
});
expect(findBlobButtonGroup().props('canLock')).toBe(canLock);
},
);
});
});
});
......@@ -7,6 +7,10 @@ RSpec.describe 'Copy as GFM', :js do
include RepoHelpers
include ActionView::Helpers::JavaScriptHelper
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350454
end
describe 'Copying rendered GFM' do
before do
@feat = MarkdownFeature.new
......
......@@ -34,26 +34,23 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
find("#L5").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: "LC5")))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
find("#L5").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: "LC5")))
end
end
......@@ -73,26 +70,23 @@ RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
find("#L5").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: "LC5")))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
find("#L5").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: "LC5")))
end
end
end
......
......@@ -29,6 +29,10 @@ RSpec.describe 'File blob', :js do
).execute
end
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350455
end
context 'Ruby file' do
before do
visit_blob('files/ruby/popen.rb')
......
......@@ -340,6 +340,7 @@ RSpec.describe "User browses files" do
let(:newrev) { project.repository.commit('master').sha }
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350456
create_file_in_repo(project, 'master', 'master', filename, 'Test file')
path = File.join('master', filename)
......@@ -355,6 +356,7 @@ RSpec.describe "User browses files" do
context "when browsing a raw file" do
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350456
path = File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)
visit(project_blob_path(project, path))
......
......@@ -35,7 +35,7 @@ RSpec.describe 'Projects > Files > User browses LFS files' do
expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
expect(page).to have_content 'size 1575078'
expect(page).not_to have_content 'Download (1.5 MB)'
expect(page).not_to have_content 'Download (1.50 MiB)'
end
end
......@@ -56,7 +56,7 @@ RSpec.describe 'Projects > Files > User browses LFS files' do
click_link('lfs')
click_link('lfs_object.iso')
expect(page).to have_content('Download (1.5 MB)')
expect(page).to have_content('Download (1.50 MiB)')
expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1')
expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897')
expect(page).not_to have_content('size 1575078')
......@@ -88,7 +88,7 @@ RSpec.describe 'Projects > Files > User browses LFS files' do
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')
expect(page).not_to have_css('button[data-testid="edit"')
end
end
end
......
......@@ -15,6 +15,7 @@ RSpec.describe 'Projects > Files > User deletes files', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/349953
sign_in(user)
end
......
......@@ -150,7 +150,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect_fork_prompt
click_link_or_button('Fork project')
click_link_or_button('Fork')
expect_fork_status
......@@ -169,7 +169,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect_fork_prompt
click_link_or_button('Fork project')
click_link_or_button('Fork')
expect_fork_status
......@@ -183,7 +183,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
click_link_or_button('Edit')
expect_fork_prompt
click_link_or_button('Fork project')
click_link_or_button('Fork')
find('.file-editor', match: :first)
......@@ -214,7 +214,7 @@ RSpec.describe 'Projects > Files > User edits files', :js do
click_link('.gitignore')
click_link_or_button('Edit')
expect(page).not_to have_link('Fork project')
expect(page).not_to have_link('Fork')
find('#editor')
set_editor_value('*.rbca')
......
......@@ -17,6 +17,7 @@ RSpec.describe 'Projects > Files > User replaces files', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/349953
sign_in(user)
end
......
......@@ -9,6 +9,7 @@ RSpec.describe 'View on environment', :js do
let(:user) { project.creator }
before do
stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350457
project.add_maintainer(user)
end
......
......@@ -17,7 +17,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = `
</div>
<div
class="gl-display-none gl-sm-display-flex file-actions"
class="gl-sm-display-flex file-actions"
>
<viewer-switcher-stub
value="simple"
......
......@@ -21,6 +21,7 @@ describe('Blob viewer', () => {
setTestTimeout(2000);
beforeEach(() => {
window.gon.features = { refactorBlobViewer: false }; // This file is based on the old (non-refactored) blob viewer
jest.spyOn(window, 'requestIdleCallback').mockImplementation(execImmediately);
$.fn.extend(jQueryMock);
mock = new MockAdapter(axios);
......
......@@ -12,6 +12,7 @@ RSpec.describe Projects::BlobController, '(JavaScript fixtures)', type: :control
render_views
before do
stub_feature_flags(refactor_blob_viewer: false) # This fixture is only used by the legacy (non-refactored) blob viewer
sign_in(user)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
......
......@@ -338,35 +338,11 @@ describe('Blob content viewer component', () => {
deletePath: webPath,
canPushCode: pushCode,
canLock: true,
isLocked: true,
isLocked: false,
emptyRepo: empty,
});
});
it.each`
canPushCode | canDownloadCode | username | canLock
${true} | ${true} | ${'root'} | ${true}
${false} | ${true} | ${'root'} | ${false}
${true} | ${false} | ${'root'} | ${false}
${true} | ${true} | ${'peter'} | ${false}
`(
'passes the correct lock states',
async ({ canPushCode, canDownloadCode, username, canLock }) => {
gon.current_username = username;
await createComponent(
{
pushCode: canPushCode,
downloadCode: canDownloadCode,
empty,
},
mount,
);
expect(findBlobButtonGroup().props('canLock')).toBe(canLock);
},
);
it('does not render if not logged in', async () => {
isLoggedIn.mockReturnValueOnce(false);
......
......@@ -13,6 +13,7 @@ export const simpleViewerMock = {
ideForkAndEditPath: 'some_file.js/fork/ide',
canModifyBlob: true,
canCurrentUserPushToBranch: true,
archived: false,
storedExternally: false,
externalStorage: 'lfs',
rawPath: 'some_file.js',
......@@ -51,7 +52,7 @@ export const projectMock = {
nodes: [
{
id: 'test',
path: simpleViewerMock.path,
path: 'locked_file.js',
user: { id: '123', username: 'root' },
},
],
......@@ -76,6 +77,8 @@ export const blobControlsDataMock = {
blamePath: 'blame/file.js',
historyPath: 'history/file.js',
permalinkPath: 'permalink/file.js',
storedExternally: false,
externalStorage: '',
},
],
},
......
......@@ -243,7 +243,7 @@ describe('Web IDE link component', () => {
])(
'emits the correct event when an action handler is called',
async ({ props, expectedEventPayload }) => {
createComponent({ ...props, needsToFork: true });
createComponent({ ...props, needsToFork: true, disableForkModal: true });
findActionsButton().props('actions')[0].handle();
......
......@@ -35,6 +35,7 @@ RSpec.describe Types::Repository::BlobType do
:plain_data,
:can_modify_blob,
:can_current_user_push_to_branch,
:archived,
:ide_edit_path,
:external_storage_url,
:fork_and_edit_path,
......
......@@ -53,6 +53,10 @@ RSpec.describe BlobPresenter do
end
end
describe '#archived?' do
it { expect(presenter.archived?).to eq(project.archived) }
end
describe '#pipeline_editor_path' do
context 'when blob is .gitlab-ci.yml' do
before do
......
......@@ -294,11 +294,6 @@ RSpec.configure do |config|
# once the Vue issues page has feature parity with the current Haml page
stub_feature_flags(vue_issues_list: false)
# Disable `refactor_blob_viewer` as we refactor
# the blob viewer. See the follwing epic for more:
# https://gitlab.com/groups/gitlab-org/-/epics/5531
stub_feature_flags(refactor_blob_viewer: false)
# Disable `main_branch_over_master` as we migrate
# from `master` to `main` accross our codebase.
# It's done in order to preserve the concistency in tests
......
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