Merge branch 'dm-snippet-blob-viewers' into 'master'

Use blob viewers for snippets

See merge request !10747
This commit is contained in:
Sean McGivern 2017-05-01 14:20:04 +00:00
commit 0789d7aab3
31 changed files with 481 additions and 75 deletions

View file

@ -6,7 +6,7 @@ export default class BlobViewer {
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.$blobContentHolder = $('#blob-content-holder'); this.$fileHolder = $('.file-holder');
let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type'); let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
@ -82,7 +82,7 @@ export default class BlobViewer {
viewer.setAttribute('data-loaded', 'true'); viewer.setAttribute('data-loaded', 'true');
this.$blobContentHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}); });

View file

@ -356,6 +356,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show': case 'users:show':
new UserCallout(); new UserCallout();
break; break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
@ -434,6 +438,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') { if (path[2] === 'show') {
new ZenMode(); new ZenMode();
new LineHighlighter();
new BlobViewer();
} }
break; break;
case 'labels': case 'labels':

View file

@ -57,9 +57,9 @@ require('vendor/jquery.scrollTo');
} }
LineHighlighter.prototype.bindEvents = function() { LineHighlighter.prototype.bindEvents = function() {
const $blobContentHolder = $('#blob-content-holder'); const $fileHolder = $('.file-holder');
$blobContentHolder.on('click', 'a[data-line-number]', this.clickHandler); $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$blobContentHolder.on('highlight:line', this.highlightHash); $fileHolder.on('highlight:line', this.highlightHash);
}; };
LineHighlighter.prototype.highlightHash = function() { LineHighlighter.prototype.highlightHash = function() {

View file

@ -14,4 +14,8 @@ module RendersBlob
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false) html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false)
} }
end end
def override_max_blob_size(blob)
blob.override_max_size! if params[:override_max_size] == 'true'
end
end end

View file

@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end end
def show def show
@blob.override_max_size! if params[:override_max_size] == 'true' override_max_blob_size(@blob)
respond_to do |format| respond_to do |format|
format.html do format.html do

View file

@ -3,6 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
include RendersBlob
before_action :module_enabled before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
@ -55,11 +56,23 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def show def show
@note = @project.notes.new(noteable: @snippet) blob = @snippet.blob
@noteable = @snippet override_max_blob_size(blob)
@discussions = @snippet.discussions respond_to do |format|
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) format.html do
@note = @project.notes.new(noteable: @snippet)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
render 'show'
end
format.json do
render_blob_json(blob)
end
end
end end
def destroy def destroy

View file

@ -3,6 +3,7 @@ class SnippetsController < ApplicationController
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
include MarkdownPreview include MarkdownPreview
include RendersBlob
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
@ -60,6 +61,18 @@ class SnippetsController < ApplicationController
end end
def show def show
blob = @snippet.blob
override_max_blob_size(blob)
respond_to do |format|
format.html do
render 'show'
end
format.json do
render_blob_json(blob)
end
end
end end
def destroy def destroy

View file

@ -119,7 +119,15 @@ module BlobHelper
end end
def blob_raw_url def blob_raw_url
namespace_project_raw_path(@project.namespace, @project, @id) if @snippet
if @snippet.project_id
raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
else
raw_snippet_path(@snippet)
end
elsif @blob
namespace_project_raw_path(@project.namespace, @project, @id)
end
end end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
@ -212,8 +220,8 @@ module BlobHelper
clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm js-copy-blob-source-btn", title: "Copy source to clipboard") clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm js-copy-blob-source-btn", title: "Copy source to clipboard")
end end
def open_raw_file_button(path) def open_raw_blob_button
link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' } link_to icon('file-code-o'), blob_raw_url, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' }
end end
def blob_render_error_reason(viewer) def blob_render_error_reason(viewer)

View file

@ -1,6 +1,5 @@
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Linguist::BlobHelper
include CacheMarkdownField include CacheMarkdownField
include Noteable include Noteable
include Participable include Participable
@ -87,47 +86,26 @@ class Snippet < ActiveRecord::Base
] ]
end end
def data def blob
content @blob ||= Blob.decorate(SnippetBlob.new(self), nil)
end end
def hook_attrs def hook_attrs
attributes attributes
end end
def size
0
end
def file_name def file_name
super.to_s super.to_s
end end
# alias for compatibility with blobs and highlighting
def path
file_name
end
def name
file_name
end
def sanitized_file_name def sanitized_file_name
file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '') file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
end end
def mode
nil
end
def visibility_level_field def visibility_level_field
:visibility_level :visibility_level
end end
def no_highlighting?
content.lines.count > 1000
end
def notes_with_associations def notes_with_associations
notes.includes(:author) notes.includes(:author)
end end

View file

@ -0,0 +1,59 @@
class SnippetBlob
include Linguist::BlobHelper
attr_reader :snippet
def initialize(snippet)
@snippet = snippet
end
delegate :id, to: :snippet
def name
snippet.file_name
end
alias_method :path, :name
def size
data.bytesize
end
def data
snippet.content
end
def rendered_markup
return unless Gitlab::MarkupHelper.gitlab_markdown?(name)
Banzai.render_field(snippet, :content)
end
def mode
nil
end
def binary?
false
end
def load_all_data!(repository)
# No-op
end
def lfs_pointer?
false
end
def lfs_oid
nil
end
def lfs_size
nil
end
def truncated?
false
end
end

View file

@ -16,7 +16,7 @@
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= copy_blob_source_button(blob) if !blame && blob.rendered_as_text?(ignore_errors: false) = copy_blob_source_button(blob) if !blame && blob.rendered_as_text?(ignore_errors: false)
= open_raw_file_button(namespace_project_raw_path(@project.namespace, @project, @id)) = open_raw_blob_button
= view_on_environment_button(@commit.sha, @path, @environment) if @environment = view_on_environment_button(@commit.sha, @path, @environment) if @environment
.btn-group{ role: "group" }< .btn-group{ role: "group" }<

View file

@ -1,3 +1,4 @@
- blob = viewer.blob - blob = viewer.blob
- rendered_markup = blob.rendered_markup if blob.respond_to?(:rendered_markup)
.file-content.wiki .file-content.wiki
= markup(blob.name, blob.data) = markup(blob.name, blob.data, rendered: rendered_markup)

View file

@ -4,7 +4,7 @@
.project-snippets .project-snippets
%article.file-holder.snippet-file-content %article.file-holder.snippet-file-content
= render 'shared/snippets/blob', raw_path: raw_namespace_project_snippet_path(@project.namespace, @project, @snippet) = render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block .row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true = render 'award_emoji/awards_block', awardable: @snippet, inline: true

View file

@ -39,7 +39,7 @@
.blob-content .blob-content
- snippet_chunks.each do |chunk| - snippet_chunks.each do |chunk|
- unless chunk[:data].empty? - unless chunk[:data].empty?
= highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?) = highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.blob.no_highlighting?)
- else - else
.file-content.code .file-content.code
.nothing-here-block Empty file .nothing-here-block Empty file

View file

@ -1,29 +1,24 @@
- blob = @snippet.blob
.js-file-title.file-title-flex-parent .js-file-title.file-title-flex-parent
.file-header-content .file-header-content
= blob_icon @snippet.mode, @snippet.path = blob_icon blob.mode, blob.path
%strong.file-title-name %strong.file-title-name
= @snippet.path = blob.path
= copy_file_path_button(@snippet.path) = copy_file_path_button(blob.path)
%small
= number_to_human_size(blob.raw_size)
.file-actions.hidden-xs .file-actions.hidden-xs
= render 'projects/blob/viewer_switcher', blob: blob
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= copy_blob_source_button(@snippet) = copy_blob_source_button(blob)
= open_raw_file_button(raw_path) = open_raw_blob_button
- if defined?(download_path) && download_path - if defined?(download_path) && download_path
= link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' } = link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
- if @snippet.content.empty? = render 'projects/blob/content', blob: blob
.file-content.code
.nothing-here-block Empty file
- else
- if markup?(@snippet.file_name)
.file-content.wiki
- if gitlab_markdown?(@snippet.file_name)
= preserve(markdown_field(@snippet, :content))
- else
= markup(@snippet.file_name, @snippet.content)
- else
= render 'shared/file_highlight', blob: @snippet

View file

@ -3,7 +3,7 @@
= render 'shared/snippets/header' = render 'shared/snippets/header'
%article.file-holder.snippet-file-content %article.file-holder.snippet-file-content
= render 'shared/snippets/blob', raw_path: raw_snippet_path(@snippet), download_path: download_snippet_path(@snippet) = render 'shared/snippets/blob', download_path: download_snippet_path(@snippet)
.row-content-block.top-block.content-component-block .row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true = render 'award_emoji/awards_block', awardable: @snippet, inline: true

View file

@ -0,0 +1,4 @@
---
title: Use blob viewers for snippets
merge_request:
author:

View file

@ -11,6 +11,7 @@ Feature: Project Snippets
Then I should see "Snippet one" in snippets Then I should see "Snippet one" in snippets
And I should not see "Snippet two" in snippets And I should not see "Snippet two" in snippets
@javascript
Scenario: I create new project snippet Scenario: I create new project snippet
Given I click link "New snippet" Given I click link "New snippet"
And I submit new snippet "Snippet three" And I submit new snippet "Snippet three"

View file

@ -5,6 +5,7 @@ Feature: Snippets
And I have public "Personal snippet one" snippet And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet And I have private "Personal snippet private" snippet
@javascript
Scenario: I create new snippet Scenario: I create new snippet
Given I visit new snippet page Given I visit new snippet page
And I submit new snippet "Personal snippet three" And I submit new snippet "Personal snippet three"

View file

@ -3,6 +3,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
include SharedProject include SharedProject
include SharedNote include SharedNote
include SharedPaths include SharedPaths
include WaitForAjax
step 'project "Shop" have "Snippet one" snippet' do step 'project "Shop" have "Snippet one" snippet' do
create(:project_snippet, create(:project_snippet,
@ -55,9 +56,10 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "project_snippet_title", with: "Snippet three" fill_in "project_snippet_title", with: "Snippet three"
fill_in "project_snippet_file_name", with: "my_snippet.rb" fill_in "project_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three' find('.ace_editor').native.send_keys 'Content of snippet three'
end end
click_button "Create snippet" click_button "Create snippet"
wait_for_ajax
end end
step 'I should see snippet "Snippet three"' do step 'I should see snippet "Snippet three"' do
@ -79,6 +81,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "note_note", with: "Good snippet!" fill_in "note_note", with: "Good snippet!"
click_button "Comment" click_button "Comment"
end end
wait_for_ajax
end end
step 'I should see comment "Good snippet!"' do step 'I should see comment "Good snippet!"' do

View file

@ -3,6 +3,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedPaths include SharedPaths
include SharedProject include SharedProject
include SharedSnippet include SharedSnippet
include WaitForAjax
step 'I click link "Personal snippet one"' do step 'I click link "Personal snippet one"' do
click_link "Personal snippet one" click_link "Personal snippet one"
@ -26,9 +27,10 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
fill_in "personal_snippet_title", with: "Personal snippet three" fill_in "personal_snippet_title", with: "Personal snippet three"
fill_in "personal_snippet_file_name", with: "my_snippet.rb" fill_in "personal_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three' find('.ace_editor').native.send_keys 'Content of snippet three'
end end
click_button "Create snippet" click_button "Create snippet"
wait_for_ajax
end end
step 'I submit new internal snippet' do step 'I submit new internal snippet' do

View file

@ -1,13 +1,10 @@
require 'spec_helper' require 'spec_helper'
feature 'File blob', :js, feature: true do feature 'File blob', :js, feature: true do
include TreeHelper
include WaitForAjax
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
def visit_blob(path, fragment = nil) def visit_blob(path, fragment = nil)
visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment) visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
end end
context 'Ruby file' do context 'Ruby file' do
@ -39,7 +36,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the rich viewer' do
aggregate_failures do aggregate_failures do
# hides the simple viewer # hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
@ -63,7 +60,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the simple viewer' do
aggregate_failures do aggregate_failures do
# hides the rich viewer # hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]') expect(page).to have_selector('.blob-viewer[data-type="simple"]')
@ -84,7 +81,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the rich viewer' do
aggregate_failures do aggregate_failures do
# hides the simple viewer # hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
@ -105,7 +102,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the simple viewer' do
aggregate_failures do aggregate_failures do
# hides the rich viewer # hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]') expect(page).to have_selector('.blob-viewer[data-type="simple"]')

View file

@ -0,0 +1,132 @@
require 'spec_helper'
feature 'Project snippet', :js, feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) }
before do
project.team << [user, :master]
login_as(user)
end
context 'Ruby file' do
let(:file_name) { 'popen.rb' }
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
wait_for_ajax
end
it 'displays the blob' do
aggregate_failures do
# shows highlighted Ruby code
expect(page).to have_content("require 'fileutils'")
# does not show a viewer switcher
expect(page).not_to have_selector('.js-blob-viewer-switcher')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
context 'Markdown file' do
let(:file_name) { 'ruby-style-guide.md' }
let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data }
context 'visiting directly' do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows rendered Markdown
expect(page).to have_link("PEP-8")
# shows a viewer switcher
expect(page).to have_selector('.js-blob-viewer-switcher')
# shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
end
end
context 'switching to the simple viewer' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
context 'switching to the rich viewer again' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
context 'visiting with a line number anchor' do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# highlights the line in question
expect(page).to have_selector('#LC1.hll')
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end

View file

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
feature 'Create Snippet', feature: true do feature 'Create Snippet', :js, feature: true do
before do before do
login_as :user login_as :user
visit new_snippet_path visit new_snippet_path
@ -9,10 +9,11 @@ feature 'Create Snippet', feature: true do
scenario 'Authenticated user creates a snippet' do scenario 'Authenticated user creates a snippet' do
fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' find('.ace_editor').native.send_keys 'Hello World!'
end end
click_button 'Create snippet' click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title') expect(page).to have_content('My Snippet Title')
expect(page).to have_content('Hello World!') expect(page).to have_content('Hello World!')
@ -22,10 +23,11 @@ feature 'Create Snippet', feature: true do
fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name' find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name'
find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' find('.ace_editor').native.send_keys 'Hello World!'
end end
click_button 'Create snippet' click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title') expect(page).to have_content('My Snippet Title')
expect(page).to have_content('snippet+file+name') expect(page).to have_content('snippet+file+name')

View file

@ -1,10 +1,11 @@
require 'rails_helper' require 'rails_helper'
feature 'Public Snippets', feature: true do feature 'Public Snippets', :js, feature: true do
scenario 'Unauthenticated user should see public snippets' do scenario 'Unauthenticated user should see public snippets' do
public_snippet = create(:personal_snippet, :public) public_snippet = create(:personal_snippet, :public)
visit snippet_path(public_snippet) visit snippet_path(public_snippet)
wait_for_ajax
expect(page).to have_content(public_snippet.content) expect(page).to have_content(public_snippet.content)
end end

View file

@ -0,0 +1,126 @@
require 'spec_helper'
feature 'Snippet', :js, feature: true do
let(:project) { create(:project, :repository) }
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) }
context 'Ruby file' do
let(:file_name) { 'popen.rb' }
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
visit snippet_path(snippet)
wait_for_ajax
end
it 'displays the blob' do
aggregate_failures do
# shows highlighted Ruby code
expect(page).to have_content("require 'fileutils'")
# does not show a viewer switcher
expect(page).not_to have_selector('.js-blob-viewer-switcher')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
context 'Markdown file' do
let(:file_name) { 'ruby-style-guide.md' }
let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data }
context 'visiting directly' do
before do
visit snippet_path(snippet)
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows rendered Markdown
expect(page).to have_link("PEP-8")
# shows a viewer switcher
expect(page).to have_selector('.js-blob-viewer-switcher')
# shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
end
end
context 'switching to the simple viewer' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
context 'switching to the rich viewer again' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
context 'visiting with a line number anchor' do
before do
visit snippet_path(snippet, anchor: 'L1')
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# highlights the line in question
expect(page).to have_selector('#LC1.hll')
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end

View file

@ -157,6 +157,7 @@ describe BlobHelper do
describe '#blob_render_error_options' do describe '#blob_render_error_options' do
before do before do
assign(:project, project) assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path)) assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob' controller.params[:controller] = 'projects/blob'

View file

@ -1,4 +1,4 @@
#blob-content-holder .file-holder
.file-content .file-content
.line-numbers .line-numbers
- 1.upto(25) do |i| - 1.upto(25) do |i|

View file

@ -0,0 +1,47 @@
require 'spec_helper'
describe SnippetBlob, models: true do
let(:snippet) { create(:snippet) }
subject { described_class.new(snippet) }
describe '#id' do
it 'returns the snippet ID' do
expect(subject.id).to eq(snippet.id)
end
end
describe '#name' do
it 'returns the snippet file name' do
expect(subject.name).to eq(snippet.file_name)
end
end
describe '#size' do
it 'returns the data size' do
expect(subject.size).to eq(subject.data.bytesize)
end
end
describe '#data' do
it 'returns the snippet content' do
expect(subject.data).to eq(snippet.content)
end
end
describe '#rendered_markup' do
context 'when the content is GFM' do
let(:snippet) { create(:snippet, file_name: 'file.md') }
it 'returns the rendered GFM' do
expect(subject.rendered_markup).to eq(snippet.content_html)
end
end
context 'when the content is not GFM' do
it 'returns nil' do
expect(subject.rendered_markup).to be_nil
end
end
end
end

View file

@ -5,7 +5,6 @@ describe Snippet, models: true do
subject { described_class } subject { described_class }
it { is_expected.to include_module(Gitlab::VisibilityLevel) } it { is_expected.to include_module(Gitlab::VisibilityLevel) }
it { is_expected.to include_module(Linguist::BlobHelper) }
it { is_expected.to include_module(Participable) } it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
@ -241,4 +240,16 @@ describe Snippet, models: true do
end end
end end
end end
describe '#blob' do
let(:snippet) { create(:snippet) }
it 'returns a blob representing the snippet data' do
blob = snippet.blob
expect(blob).to be_a(Blob)
expect(blob.path).to eq(snippet.file_name)
expect(blob.data).to eq(snippet.content)
end
end
end end

View file

@ -21,6 +21,7 @@ describe 'projects/blob/_viewer.html.haml', :view do
before do before do
assign(:project, project) assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path)) assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob' controller.params[:controller] = 'projects/blob'