Commit 1c55b571 authored by Douwe Maan's avatar Douwe Maan

Merge branch '34509-improves-markdown-rendering-performance-for-commits-list' into 'master'

Resolve "Projects::CommitsController#show is slow partially due to SQL queries"

Closes #34509

See merge request !13762
parents 92823171 6d8e102c
module RendersCommits
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user)
commits
end
end
...@@ -2,6 +2,7 @@ require "base64" ...@@ -2,6 +2,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersCommits
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
...@@ -56,5 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -56,5 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
else else
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset) @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end end
@commits = prepare_commits_for_rendering(@commits)
end end
end end
...@@ -3,6 +3,7 @@ require 'addressable/uri' ...@@ -3,6 +3,7 @@ require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include RendersCommits
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
...@@ -50,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -50,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController
.execute(@project, @start_ref) .execute(@project, @start_ref)
if @compare if @compare
@commits = @compare.commits @commits = prepare_commits_for_rendering(@compare.commits)
@diffs = @compare.diffs(diff_options) @diffs = @compare.diffs(diff_options)
environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit } environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit }
......
class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include RendersCommits
skip_before_action :merge_request skip_before_action :merge_request
skip_before_action :ensure_ref_fetched skip_before_action :ensure_ref_fetched
...@@ -107,7 +108,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -107,7 +108,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project @target_project = @merge_request.target_project
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
@commits = @merge_request.commits @commits = prepare_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit @commit = @merge_request.diff_head_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)) @note_counts = Note.where(commit_id: @commits.map(&:id))
......
...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions include IssuableActions
include RendersNotes include RendersNotes
include RendersCommits
include ToggleAwardEmoji include ToggleAwardEmoji
include IssuableCollections include IssuableCollections
...@@ -94,7 +95,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -94,7 +95,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits def commits
# Get commits from repository # Get commits from repository
# or from cache if already merged # or from cache if already merged
@commits = @merge_request.commits @commits = prepare_commits_for_rendering(@merge_request.commits)
@note_counts = Note.where(commit_id: @commits.map(&:id)) @note_counts = Note.where(commit_id: @commits.map(&:id))
.group(:commit_id).count .group(:commit_id).count
......
...@@ -2,6 +2,7 @@ class SearchController < ApplicationController ...@@ -2,6 +2,7 @@ class SearchController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
include SearchHelper include SearchHelper
include RendersCommits
layout 'search' layout 'search'
...@@ -20,6 +21,8 @@ class SearchController < ApplicationController ...@@ -20,6 +21,8 @@ class SearchController < ApplicationController
@search_results = search_service.search_results @search_results = search_service.search_results
@search_objects = search_service.search_objects @search_objects = search_service.search_objects
render_commits if @scope == 'commits'
check_single_commit_result check_single_commit_result
end end
...@@ -38,6 +41,10 @@ class SearchController < ApplicationController ...@@ -38,6 +41,10 @@ class SearchController < ApplicationController
private private
def render_commits
@search_objects = prepare_commits_for_rendering(@search_objects)
end
def check_single_commit_result def check_single_commit_result
if @search_results.single_commit_result? if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first only_commit = @search_results.objects('commits').first
......
...@@ -21,25 +21,28 @@ module MarkupHelper ...@@ -21,25 +21,28 @@ module MarkupHelper
end end
# Use this in places where you would normally use link_to(gfm(...), ...). # Use this in places where you would normally use link_to(gfm(...), ...).
# def link_to_markdown(body, url, html_options = {})
return '' if body.blank?
link_to_html(markdown(body, pipeline: :single_line), url, html_options)
end
def link_to_markdown_field(object, field, url, html_options = {})
rendered_field = markdown_field(object, field)
link_to_html(rendered_field, url, html_options)
end
# It solves a problem occurring with nested links (i.e. # It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be # "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like # interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is # "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to # not linked any more). link_to_html corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e. # explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>"). # "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {}) def link_to_html(redacted, url, html_options = {})
return '' if body.blank? fragment = Nokogiri::HTML::DocumentFragment.parse(redacted)
context = {
project: @project,
current_user: (current_user if defined?(current_user)),
pipeline: :single_line
}
gfm_body = Banzai.render(body, context)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a' if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`. # Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link. # Replace it with our requested link.
...@@ -82,7 +85,10 @@ module MarkupHelper ...@@ -82,7 +85,10 @@ module MarkupHelper
def markdown_field(object, field) def markdown_field(object, field)
object = object.for_display if object.respond_to?(:for_display) object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present? return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field) html = Banzai.render_field(object, field)
prepare_for_rendering(html, object.banzai_render_context(field)) prepare_for_rendering(html, object.banzai_render_context(field))
......
...@@ -16,6 +16,8 @@ class Commit ...@@ -16,6 +16,8 @@ class Commit
participant :notes_with_associations participant :notes_with_associations
attr_accessor :project, :author attr_accessor :project, :author
attr_accessor :redacted_description_html
attr_accessor :redacted_title_html
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
...@@ -26,6 +28,13 @@ class Commit ...@@ -26,6 +28,13 @@ class Commit
# The SHA can be between 7 and 40 hex characters. # The SHA can be between 7 and 40 hex characters.
COMMIT_SHA_PATTERN = '\h{7,40}'.freeze COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
context[:author] = self.author if self.author
context
end
class << self class << self
def decorate(commits, project) def decorate(commits, project)
commits.map do |commit| commits.map do |commit|
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
= author_avatar(commit, size: 36) = author_avatar(commit, size: 36)
.commit-row-title .commit-row-title
%span.item-title.str-truncated-100 %span.item-title.str-truncated-100
= link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
.pull-right .pull-right
= link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
&nbsp; &nbsp;
......
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
= link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha" = link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
&middot; &middot;
%span.str-truncated %span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" = link_to_markdown commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
&middot; &middot;
#{time_ago_with_tooltip(commit.committed_date)} #{time_ago_with_tooltip(commit.committed_date)}
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.commit-detail .commit-detail
.commit-content .commit-content
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message item-title" = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline %span.commit-row-message.visible-xs-inline
&middot; &middot;
= commit.short_id = commit.short_id
...@@ -28,7 +28,8 @@ ...@@ -28,7 +28,8 @@
- if commit.description? - if commit.description?
%pre.commit-row-description.js-toggle-content %pre.commit-row-description.js-toggle-content
= preserve(markdown(commit.description, pipeline: :single_line, author: commit.author)) = preserve(markdown_field(commit, :description))
.commiter .commiter
- commit_author_link = commit_author_link(commit, avatar: false, size: 24) - commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = time_ago_with_tooltip(commit.committed_date) - commit_timeago = time_ago_with_tooltip(commit.committed_date)
......
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha" = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha"
&nbsp; &nbsp;
%span.str-truncated %span.str-truncated
= link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message")
.pull-right .pull-right
#{time_ago_with_tooltip(commit.committed_date)} #{time_ago_with_tooltip(commit.committed_date)}
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
%span.flex-truncate-child %span.flex-truncate-child
- if commit_title = deployment.commit_title - if commit_title = deployment.commit_title
= author_avatar(deployment.commit, size: 20) = author_avatar(deployment.commit, size: 20)
= link_to_gfm commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message" = link_to_markdown commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
- else - else
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
%span.str-truncated %span.str-truncated
= link_to_gfm commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link" = link_to_markdown commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link"
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
commented commented
- if note.system - if note.system
%span.system-note-message %span.system-note-message
= note.redacted_note_html = markdown_field(note, :note)
%a{ href: "##{dom_id(note)}" } %a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- unless note.system? - unless note.system?
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
= render 'projects/notes/actions', note: note, note_editable: note_editable = render 'projects/notes/actions', note: note, note_editable: note_editable
.note-body{ class: note_editable ? 'js-task-list-container' : '' } .note-body{ class: note_editable ? 'js-task-list-container' : '' }
.note-text.md .note-text.md
= note.redacted_note_html = markdown_field(note, :note)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago') = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } .original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
#{note.note} #{note.note}
......
...@@ -31,8 +31,7 @@ ...@@ -31,8 +31,7 @@
- if show_last_commit_as_description - if show_last_commit_as_description
.description.prepend-top-5 .description.prepend-top-5
= link_to_gfm project.commit.title, project_commit_path(project, project.commit), = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
class: "commit-row-message"
- elsif project.description.present? - elsif project.description.present?
.description.prepend-top-5 .description.prepend-top-5
= markdown_field(project, :description) = markdown_field(project, :description)
......
---
title: Improves markdown rendering performance for commit lists.
merge_request:
author:
type: other
...@@ -114,9 +114,6 @@ def instrument_classes(instrumentation) ...@@ -114,9 +114,6 @@ def instrument_classes(instrumentation)
# This is a Rails scope so we have to instrument it manually. # This is a Rails scope so we have to instrument it manually.
instrumentation.instrument_method(Project, :visible_to_user) instrumentation.instrument_method(Project, :visible_to_user)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/34509
instrumentation.instrument_method(MarkupHelper, :link_to_gfm)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159 # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits) instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
......
module Banzai
module CommitRenderer
ATTRIBUTES = [:description, :title].freeze
def self.render(commits, project, user = nil)
obj_renderer = ObjectRenderer.new(project, user)
ATTRIBUTES.each { |attr| obj_renderer.render(commits, attr) }
end
end
end
...@@ -38,7 +38,7 @@ module Banzai ...@@ -38,7 +38,7 @@ module Banzai
objects.each_with_index do |object, index| objects.each_with_index do |object, index|
redacted_data = redacted[index] redacted_data = redacted[index]
object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) # rubocop:disable GitlabSecurity/PublicSend object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) # rubocop:disable GitlabSecurity/PublicSend
object.user_visible_reference_count = redacted_data[:visible_reference_count] object.user_visible_reference_count = redacted_data[:visible_reference_count] if object.respond_to?(:user_visible_reference_count)
end end
end end
......
...@@ -36,6 +36,10 @@ module Banzai ...@@ -36,6 +36,10 @@ module Banzai
# The context to use is managed by the object and cannot be changed. # The context to use is managed by the object and cannot be changed.
# Use #render, passing it the field text, if a custom rendering is needed. # Use #render, passing it the field text, if a custom rendering is needed.
def self.render_field(object, field) def self.render_field(object, field)
unless object.respond_to?(:cached_markdown_fields)
return cacheless_render_field(object, field)
end
object.refresh_markdown_cache!(do_update: update_object?(object)) unless object.cached_html_up_to_date?(field) object.refresh_markdown_cache!(do_update: update_object?(object)) unless object.cached_html_up_to_date?(field)
object.cached_html_for(field) object.cached_html_for(field)
......
...@@ -52,12 +52,71 @@ describe MarkupHelper do ...@@ -52,12 +52,71 @@ describe MarkupHelper do
end end
end end
describe '#link_to_gfm' do describe '#markdown_field' do
let(:attribute) { :title }
describe 'with already redacted attribute' do
it 'returns the redacted attribute' do
commit.redacted_title_html = 'commit title'
expect(Banzai).not_to receive(:render_field)
expect(helper.markdown_field(commit, attribute)).to eq('commit title')
end
end
describe 'without redacted attribute' do
it 'renders the markdown value' do
expect(Banzai).to receive(:render_field).with(commit, attribute).and_call_original
helper.markdown_field(commit, attribute)
end
end
end
describe '#link_to_markdown_field' do
let(:link) { '/commits/0a1b2c3d' }
let(:issues) { create_list(:issue, 2, project: project) }
it 'handles references nested in links with all the text' do
allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real")
actual = helper.link_to_markdown_field(commit, :title, link)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
expect(doc.errors).to be_empty
# Leading commit link
expect(doc.css('a')[0].attr('href')).to eq link
expect(doc.css('a')[0].text).to eq 'This should finally fix '
# First issue link
expect(doc.css('a')[1].attr('href'))
.to eq project_issue_path(project, issues[0])
expect(doc.css('a')[1].text).to eq issues[0].to_reference
# Internal commit link
expect(doc.css('a')[2].attr('href')).to eq link
expect(doc.css('a')[2].text).to eq ' and '
# Second issue link
expect(doc.css('a')[3].attr('href'))
.to eq project_issue_path(project, issues[1])
expect(doc.css('a')[3].text).to eq issues[1].to_reference
# Trailing commit link
expect(doc.css('a')[4].attr('href')).to eq link
expect(doc.css('a')[4].text).to eq ' for real'
end
end
describe '#link_to_markdown' do
let(:link) { '/commits/0a1b2c3d' } let(:link) { '/commits/0a1b2c3d' }
let(:issues) { create_list(:issue, 2, project: project) } let(:issues) { create_list(:issue, 2, project: project) }
it 'handles references nested in links with all the text' do it 'handles references nested in links with all the text' do
actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link) actual = helper.link_to_markdown("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", link)
doc = Nokogiri::HTML.parse(actual) doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup # Make sure we didn't create invalid markup
...@@ -87,7 +146,7 @@ describe MarkupHelper do ...@@ -87,7 +146,7 @@ describe MarkupHelper do
end end
it 'forwards HTML options' do it 'forwards HTML options' do
actual = helper.link_to_gfm("Fixed in #{commit.id}", link, class: 'foo') actual = helper.link_to_markdown("Fixed in #{commit.id}", link, class: 'foo')
doc = Nokogiri::HTML.parse(actual) doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v| expect(doc.css('a')).to satisfy do |v|
...@@ -98,23 +157,43 @@ describe MarkupHelper do ...@@ -98,23 +157,43 @@ describe MarkupHelper do
it "escapes HTML passed in as the body" do it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(helper.link_to_gfm(actual, link)) expect(helper.link_to_markdown(actual, link))
.to match('&lt;h1&gt;test&lt;/h1&gt;') .to match('&lt;h1&gt;test&lt;/h1&gt;')
end end
it 'ignores reference links when they are the entire body' do it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference text = issues[0].to_reference
act = helper.link_to_gfm(text, '/foo') act = helper.link_to_markdown(text, '/foo')
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>) expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end end
it 'replaces commit message with emoji to link' do it 'replaces commit message with emoji to link' do
actual = link_to_gfm(':book: Book', '/foo') actual = link_to_markdown(':book: Book', '/foo')
expect(actual) expect(actual)
.to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>' .to eq '<gl-emoji title="open book" data-name="book" data-unicode-version="6.0">📖</gl-emoji><a href="/foo"> Book</a>'
end end
end end
describe '#link_to_html' do
it 'wraps the rendered content in a link' do
link = '/commits/0a1b2c3d'
issue = create(:issue, project: project)
rendered = helper.markdown("This should finally fix #{issue.to_reference} for real", pipeline: :single_line)
doc = Nokogiri::HTML.parse(rendered)
expect(doc.css('a')[0].attr('href'))
.to eq project_issue_path(project, issue)
expect(doc.css('a')[0].text).to eq issue.to_reference
wrapped = helper.link_to_html(rendered, link)
doc = Nokogiri::HTML.parse(wrapped)
expect(doc.css('a')[0].attr('href')).to eq link
expect(doc.css('a')[0].text).to eq 'This should finally fix '
end
end
describe '#render_wiki_content' do describe '#render_wiki_content' do
before do before do
@wiki = double('WikiPage') @wiki = double('WikiPage')
......
require 'spec_helper'
describe Banzai::CommitRenderer do
describe '.render' do
it 'renders a commit description and title' do
user = double(:user)
project = create(:project, :repository)
expect(Banzai::ObjectRenderer).to receive(:new).with(project, user).and_call_original
described_class::ATTRIBUTES.each do |attr|
expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr)
end
described_class.render([project.commit], project, user)
end
end
end
require 'spec_helper' require 'spec_helper'
describe Banzai::ObjectRenderer do describe Banzai::ObjectRenderer do
let(:project) { create(:project) } let(:project) { create(:project, :repository) }
let(:user) { project.owner } let(:user) { project.owner }
let(:renderer) { described_class.new(project, user, custom_value: 'value') } let(:renderer) { described_class.new(project, user, custom_value: 'value') }
let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) } let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_VERSION) }
describe '#render' do describe '#render' do
context 'with cache' do
it 'renders and redacts an Array of objects' do it 'renders and redacts an Array of objects' do
renderer.render([object], :note) renderer.render([object], :note)
...@@ -20,8 +21,8 @@ describe Banzai::ObjectRenderer do ...@@ -20,8 +21,8 @@ describe Banzai::ObjectRenderer do
renderer.render([object], :note) renderer.render([object], :note)
end end
it 'retrieves field content using Banzai.render_field' do it 'retrieves field content using Banzai::Renderer.render_field' do
expect(Banzai).to receive(:render_field).with(object, :note).and_call_original expect(Banzai::Renderer).to receive(:render_field).with(object, :note).and_call_original
renderer.render([object], :note) renderer.render([object], :note)
end end
...@@ -50,4 +51,27 @@ describe Banzai::ObjectRenderer do ...@@ -50,4 +51,27 @@ describe Banzai::ObjectRenderer do
renderer.render([object], :note) renderer.render([object], :note)
end end
end end
context 'without cache' do
let(:commit) { project.commit }
it 'renders and redacts an Array of objects' do
renderer.render([commit], :title)
expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
it 'calls Banzai::Redactor to perform redaction' do
expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
renderer.render([commit], :title)
end
it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do
expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title).and_call_original
renderer.render([commit], :title)
end
end
end
end end
...@@ -4,6 +4,7 @@ describe Banzai::Renderer do ...@@ -4,6 +4,7 @@ describe Banzai::Renderer do
def fake_object(fresh:) def fake_object(fresh:)
object = double('object') object = double('object')
allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(true)
allow(object).to receive(:cached_html_up_to_date?).with(:field).and_return(fresh) allow(object).to receive(:cached_html_up_to_date?).with(:field).and_return(fresh)
allow(object).to receive(:cached_html_for).with(:field).and_return('field_html') allow(object).to receive(:cached_html_for).with(:field).and_return('field_html')
...@@ -12,6 +13,18 @@ describe Banzai::Renderer do ...@@ -12,6 +13,18 @@ describe Banzai::Renderer do
describe '#render_field' do describe '#render_field' do
let(:renderer) { described_class } let(:renderer) { described_class }
context 'without cache' do
let(:commit) { create(:project, :repository).commit }
it 'returns cacheless render field' do
expect(renderer).to receive(:cacheless_render_field).with(commit, :title)
renderer.render_field(commit, :title)
end
end
context 'with cache' do
subject { renderer.render_field(object, :field) } subject { renderer.render_field(object, :field) }
context 'with a stale cache' do context 'with a stale cache' do
...@@ -34,4 +47,5 @@ describe Banzai::Renderer do ...@@ -34,4 +47,5 @@ describe Banzai::Renderer do
end end
end end
end end
end
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