2019-05-18 00:54:41 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-11-03 12:29:30 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
describe CacheMarkdownField do
|
|
|
|
# The minimum necessary ActiveModel to test this concern
|
|
|
|
class ThingWithMarkdownFields
|
|
|
|
include ActiveModel::Model
|
|
|
|
include ActiveModel::Dirty
|
|
|
|
|
|
|
|
include ActiveModel::Serialization
|
|
|
|
|
|
|
|
class_attribute :attribute_names
|
|
|
|
self.attribute_names = []
|
|
|
|
|
|
|
|
def attributes
|
|
|
|
attribute_names.each_with_object({}) do |name, hsh|
|
|
|
|
hsh[name.to_s] = send(name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
extend ActiveModel::Callbacks
|
2017-08-17 22:00:37 +05:30
|
|
|
define_model_callbacks :create, :update
|
2016-11-03 12:29:30 +05:30
|
|
|
|
|
|
|
include CacheMarkdownField
|
|
|
|
cache_markdown_field :foo
|
|
|
|
cache_markdown_field :baz, pipeline: :single_line
|
2019-05-18 00:54:41 +05:30
|
|
|
cache_markdown_field :zoo, whitelisted: true
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def self.add_attr(name)
|
|
|
|
self.attribute_names += [name]
|
|
|
|
define_attribute_methods(name)
|
|
|
|
attr_reader(name)
|
|
|
|
define_method("#{name}=") do |value|
|
|
|
|
write_attribute(name, value)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
add_attr :cached_markdown_version
|
|
|
|
|
2019-05-18 00:54:41 +05:30
|
|
|
[:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name|
|
2017-08-17 22:00:37 +05:30
|
|
|
add_attr(name)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(*)
|
|
|
|
super
|
|
|
|
|
|
|
|
# Pretend new is load
|
|
|
|
clear_changes_information
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
def read_attribute(name)
|
|
|
|
instance_variable_get("@#{name}")
|
|
|
|
end
|
|
|
|
|
|
|
|
def write_attribute(name, value)
|
|
|
|
send("#{name}_will_change!") unless value == read_attribute(name)
|
|
|
|
instance_variable_set("@#{name}", value)
|
|
|
|
end
|
|
|
|
|
2016-11-03 12:29:30 +05:30
|
|
|
def save
|
2017-08-17 22:00:37 +05:30
|
|
|
run_callbacks :update do
|
2016-11-03 12:29:30 +05:30
|
|
|
changes_applied
|
|
|
|
end
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
|
|
|
|
def has_attribute?(attr_name)
|
|
|
|
attribute_names.include?(attr_name)
|
|
|
|
end
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def thing_subclass(new_attr)
|
|
|
|
Class.new(ThingWithMarkdownFields) { add_attr(new_attr) }
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
let(:markdown) { '`Foo`' }
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:html) { '<p dir="auto"><code>Foo</code></p>' }
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
let(:updated_markdown) { '`Bar`' }
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' }
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
|
|
|
|
let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_commonmark_sourcepos_disabled
|
|
|
|
end
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
describe '.attributes' do
|
2019-05-18 00:54:41 +05:30
|
|
|
it 'excludes cache attributes that is blacklisted by default' do
|
|
|
|
expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html])
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'an unchanged markdown field' do
|
|
|
|
before do
|
|
|
|
thing.foo = thing.foo
|
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
it { expect(thing.foo).to eq(markdown) }
|
|
|
|
it { expect(thing.foo_html).to eq(html) }
|
|
|
|
it { expect(thing.foo_html_changed?).not_to be_truthy }
|
2019-03-02 22:35:43 +05:30
|
|
|
it { expect(thing.cached_markdown_version).to eq(cache_version) }
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'a changed markdown field' do
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
before do
|
|
|
|
thing.foo = updated_markdown
|
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it { expect(thing.foo_html).to eq(updated_html) }
|
|
|
|
it { expect(thing.cached_markdown_version).to eq(cache_version) }
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
context 'when a markdown field is set repeatedly to an empty string' do
|
|
|
|
it do
|
|
|
|
expect(thing).to receive(:refresh_markdown_cache).once
|
|
|
|
thing.foo = ''
|
|
|
|
thing.save
|
|
|
|
thing.foo = ''
|
|
|
|
thing.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a markdown field is set repeatedly to a string which renders as empty html' do
|
|
|
|
it do
|
|
|
|
expect(thing).to receive(:refresh_markdown_cache).once
|
|
|
|
thing.foo = '[//]: # (This is also a comment.)'
|
|
|
|
thing.save
|
|
|
|
thing.foo = '[//]: # (This is also a comment.)'
|
|
|
|
thing.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
context 'when a markdown field and html field are both changed' do
|
|
|
|
it do
|
|
|
|
expect(thing).not_to receive(:refresh_markdown_cache)
|
|
|
|
thing.foo = '_look over there!_'
|
|
|
|
thing.foo_html = '<em>look over there!</em>'
|
|
|
|
thing.save
|
|
|
|
end
|
|
|
|
end
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
context 'a non-markdown field changed' do
|
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
before do
|
|
|
|
thing.bar = 'OK'
|
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it { expect(thing.bar).to eq('OK') }
|
|
|
|
it { expect(thing.foo).to eq(markdown) }
|
|
|
|
it { expect(thing.foo_html).to eq(html) }
|
|
|
|
it { expect(thing.cached_markdown_version).to eq(cache_version) }
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'version is out of date' do
|
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: updated_markdown, foo_html: html, cached_markdown_version: nil) }
|
|
|
|
|
2016-11-03 12:29:30 +05:30
|
|
|
before do
|
2017-08-17 22:00:37 +05:30
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it { expect(thing.foo_html).to eq(updated_html) }
|
2019-03-02 22:35:43 +05:30
|
|
|
it { expect(thing.cached_markdown_version).to eq(cache_version) }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '#cached_html_up_to_date?' do
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
subject { thing.cached_html_up_to_date?(:foo) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false when the version is absent' do
|
|
|
|
thing.cached_markdown_version = nil
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_falsy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false when the cached version is too old' do
|
|
|
|
thing.cached_markdown_version = cache_version - 1
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_falsy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false when the cached version is in future' do
|
|
|
|
thing.cached_markdown_version = cache_version + 1
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_falsy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false when the local version was bumped' do
|
|
|
|
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
|
|
|
|
thing.cached_markdown_version = cache_version
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_falsy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns true when the local version is default' do
|
|
|
|
thing.cached_markdown_version = cache_version
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_truthy
|
|
|
|
end
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns true when the cached version is just right' do
|
|
|
|
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
|
|
|
|
thing.cached_markdown_version = cache_version + 2
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_truthy
|
|
|
|
end
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false if markdown has been changed but html has not' do
|
|
|
|
thing.foo = updated_html
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_falsy
|
|
|
|
end
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns true if markdown has not been changed but html has' do
|
|
|
|
thing.foo_html = updated_html
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_truthy
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns true if markdown and html have both been changed' do
|
|
|
|
thing.foo = updated_markdown
|
|
|
|
thing.foo_html = updated_html
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to be_truthy
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'returns false if the markdown field is set but the html is not' do
|
|
|
|
thing.foo_html = nil
|
|
|
|
|
|
|
|
is_expected.to be_falsy
|
2018-10-15 14:42:47 +05:30
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
describe '#latest_cached_markdown_version' do
|
|
|
|
subject { thing.latest_cached_markdown_version }
|
|
|
|
|
|
|
|
it 'returns default version' do
|
2018-10-15 14:42:47 +05:30
|
|
|
thing.cached_markdown_version = nil
|
2019-03-02 22:35:43 +05:30
|
|
|
is_expected.to eq(cache_version)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe '#refresh_markdown_cache' do
|
2017-08-17 22:00:37 +05:30
|
|
|
before do
|
|
|
|
thing.foo = updated_markdown
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'fills all html fields' do
|
|
|
|
thing.refresh_markdown_cache
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(thing.foo_html).to eq(updated_html)
|
|
|
|
expect(thing.foo_html_changed?).to be_truthy
|
|
|
|
expect(thing.baz_html_changed?).to be_truthy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'does not save the result' do
|
|
|
|
expect(thing).not_to receive(:update_columns)
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
thing.refresh_markdown_cache
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
it 'updates the markdown cache version' do
|
|
|
|
thing.cached_markdown_version = nil
|
|
|
|
thing.refresh_markdown_cache
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
expect(thing.cached_markdown_version).to eq(cache_version)
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#refresh_markdown_cache!' do
|
2019-03-02 22:35:43 +05:30
|
|
|
let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
before do
|
|
|
|
thing.foo = updated_markdown
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'fills all html fields' do
|
|
|
|
thing.refresh_markdown_cache!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
expect(thing.foo_html).to eq(updated_html)
|
|
|
|
expect(thing.foo_html_changed?).to be_truthy
|
|
|
|
expect(thing.baz_html_changed?).to be_truthy
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'skips saving if not persisted' do
|
|
|
|
expect(thing).to receive(:persisted?).and_return(false)
|
|
|
|
expect(thing).not_to receive(:update_columns)
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
thing.refresh_markdown_cache!
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
it 'saves the changes using #update_columns' do
|
|
|
|
expect(thing).to receive(:persisted?).and_return(true)
|
|
|
|
expect(thing).to receive(:update_columns)
|
2019-05-18 00:54:41 +05:30
|
|
|
.with(
|
|
|
|
"foo_html" => updated_html,
|
|
|
|
"baz_html" => "",
|
|
|
|
"zoo_html" => "",
|
|
|
|
"cached_markdown_version" => cache_version
|
|
|
|
)
|
2018-10-15 14:42:47 +05:30
|
|
|
|
2019-03-02 22:35:43 +05:30
|
|
|
thing.refresh_markdown_cache!
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '#banzai_render_context' do
|
2017-08-17 22:00:37 +05:30
|
|
|
subject(:context) { thing.banzai_render_context(:foo) }
|
|
|
|
|
|
|
|
it 'sets project to nil if the object lacks a project' do
|
|
|
|
is_expected.to have_key(:project)
|
2016-11-03 12:29:30 +05:30
|
|
|
expect(context[:project]).to be_nil
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'excludes author if the object lacks an author' do
|
|
|
|
is_expected.not_to have_key(:author)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'raises if the context for an unrecognised field is requested' do
|
|
|
|
expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'includes the pipeline' do
|
|
|
|
baz = thing.banzai_render_context(:baz)
|
|
|
|
|
|
|
|
expect(baz[:pipeline]).to eq(:single_line)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'returns copies of the context template' do
|
|
|
|
template = thing.cached_markdown_fields[:baz]
|
|
|
|
copy = thing.banzai_render_context(:baz)
|
|
|
|
|
2016-11-03 12:29:30 +05:30
|
|
|
expect(copy).not_to be(template)
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'with a project' do
|
2018-12-05 23:21:45 +05:30
|
|
|
let(:project) { create(:project, group: create(:group)) }
|
|
|
|
let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) }
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'sets the project in the context' do
|
|
|
|
is_expected.to have_key(:project)
|
2018-12-05 23:21:45 +05:30
|
|
|
expect(context[:project]).to eq(project)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'invalidates the cache when project changes' do
|
|
|
|
thing.project = :new_project
|
2016-11-03 12:29:30 +05:30
|
|
|
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(thing.foo_html).to eq(updated_html)
|
|
|
|
expect(thing.baz_html).to eq(updated_html)
|
2019-03-02 22:35:43 +05:30
|
|
|
expect(thing.cached_markdown_version).to eq(cache_version)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'with an author' do
|
|
|
|
let(:thing) { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author_value) }
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'sets the author in the context' do
|
|
|
|
is_expected.to have_key(:author)
|
|
|
|
expect(context[:author]).to eq(:author_value)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
it 'invalidates the cache when author changes' do
|
|
|
|
thing.author = :new_author
|
2016-11-03 12:29:30 +05:30
|
|
|
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
thing.save
|
2016-11-03 12:29:30 +05:30
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(thing.foo_html).to eq(updated_html)
|
|
|
|
expect(thing.baz_html).to eq(updated_html)
|
2019-03-02 22:35:43 +05:30
|
|
|
expect(thing.cached_markdown_version).to eq(cache_version)
|
2016-11-03 12:29:30 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|