require 'ballonizer'
require 'rspec-html-matchers'
# Avoid to use equivalent-xml, the specs break with cosmetic changes this way
require 'equivalent-xml'
require 'stringio'
require 'rexml/document'
require 'nokogiri'
# make the changes in the BD restricted to the example
RSpec.configure do |c|
c.around(:each) do |example|
DB.transaction(:rollback=>:always){example.run}
end
end
RSpec::Matchers.define :exist_in_filesystem do
match do | actual |
if actual.respond_to? :all?
actual.all? { | filename | File.exist?(filename) }
else
File.exist? actual
end
end
failure_message_for_should do | actual |
if actual.respond_to? :all?
"expected #{actual} to be a list of absolute filepaths for existing files or directories"
else
"expected #{actual} to be a absolute filepath for an existing file or directory"
end
end
description do
"be a list of absolute paths for existing files or directories"
end
end
# TODO: check the font-size, the position and the size of the ballon
# TODO: check the src of the image inside the container (not so easy
# because the src is relative in the html but absolute in the database)
RSpec::Matchers.define :have_ballons_as_submitted_by do | expected |
match do | actual |
expected.each do | img_src, ballons |
expect(actual).to(have_tag('.ballonizer_image_container') do
with_tag('img')
ballons.each do | b |
with_tag('span', { text: b['text'], with: { class: 'ballonizer_ballon' }})
end
end)
end
end
description do
'have ballons as the ones added by the last submit'
end
end
describe Ballonizer do
DB = Sequel.sqlite
def self.populate_ballonizer_tables_with_test_data(db, time)
db[:images].insert({img_src: 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/1.jpg'})
db[:images].insert({img_src: 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/2.jpg'})
db[:images].insert({img_src: 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/3.jpg'})
db[:ballons].insert({ text: 'first ballon of first image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
db[:ballons].insert({ text: 'second ballon of first image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
db[:ballons].insert({ text: 'first ballon of second image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
db[:ballons].insert({ text: 'second ballon of second image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
db[:ballons].insert({ text: 'first ballon of third image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
db[:ballons].insert({ text: 'second ballon of third image',
top: 0, left: 0, width: 0.5, height: 0.5,
font_size: 16 })
# both ballons added in the first version
db[:ballonized_image_versions].insert({image_id: 1, version: 1, time: time})
db[:ballonized_image_ballons].insert({image_id: 1, version: 1, ballon_id: 1})
db[:ballonized_image_ballons].insert({image_id: 1, version: 1, ballon_id: 2})
# second ballon added in the second version
db[:ballonized_image_versions].insert({image_id: 2, version: 1, time: time})
db[:ballonized_image_ballons].insert({image_id: 2, version: 1, ballon_id: 3})
db[:ballonized_image_versions].insert({image_id: 2, version: 2, time: time})
db[:ballonized_image_ballons].insert({image_id: 2, version: 2, ballon_id: 3})
db[:ballonized_image_ballons].insert({image_id: 2, version: 2, ballon_id: 4})
# both added in first version, but second removed in the second version
db[:ballonized_image_versions].insert({image_id: 3, version: 1, time: time})
db[:ballonized_image_ballons].insert({image_id: 3, version: 1, ballon_id: 5})
db[:ballonized_image_ballons].insert({image_id: 3, version: 1, ballon_id: 6})
db[:ballonized_image_versions].insert({image_id: 3, version: 2, time: time})
db[:ballonized_image_ballons].insert({image_id: 3, version: 2, ballon_id: 5})
end
Ballonizer.create_tables(DB)
self.populate_ballonizer_tables_with_test_data(DB, Time.at(0))
def deep_copy(v)
JSON.parse(JSON.generate(v))
end
# definitions to be overriden in need, but who doesn't need a *_example
# counterpart (are so simple that clone and change isn't pratical)
let (:mime_type) { 'application/xhtml+xml' }
let (:ballonize_page_settings_arg) { {} }
# Definitions ending with '_example' are to be cloned and defined in a
# context without the sufix. Definitions without the sufix are used in the
# specs and may require the definition of some without '_example' counterparts.
let (:ballonizer_settings_example) do
{}
end
let (:ballonizer_new_args_example) do
[DB, ballonizer_settings]
end
# TODO: This isn't a valid Rack env, to turn it in a valid env will be
# necessary to add all obrigatory 'rack.' variables described in:
# http://rack.rubyforge.org/doc/SPEC.html
# (the missing are the hijack related and the .errors)
let (:env_example) do
form_data = StringIO.new(Addressable::URI.form_encode({
ballonizer_data: JSON.generate(submit_hash)
}))
{ 'HTTP_HOST' => 'proxysite.net',
'SCRIPT_NAME' => '',
'PATH_INFO' => '/pt_BR-ballon_translate/comic/',
'QUERY_STRING' => '',
'SERVER_NAME' => 'proxysite.net',
'SERVER_PORT' => '80',
'REQUEST_METHOD' => 'POST',
'HTTP_VERSION' => 'HTTP/1.1',
'CONTENT_LENGTH' => form_data.size.to_s,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'rack.url_scheme' => 'http',
'rack.input' => form_data,
'rack.version' => [1, 0],
'rack.multithread' => false,
'rack.multiprocess' => false,
'rack.run_once' => false
}
end
let (:original_page_example) do
doc = <<-END
A title
END
Nokogiri::XML::Document.parse(doc)
end
let (:page_url_example) do
'http://comic-translation.com/tr/pt-BR/a_comic/'
end
let (:submit_json_example) do
'{"http://imgs.xkcd.com/comics/cells.png":[{"left":0,"top":0,"width":1,"height":0.23837209302325582,"font_size":15,"text":"When you see a claim that a common drug or vitamin \"kills cancer cells in a petri dish\", keep in mind:"},{"left":0.0963302752293578,"top":0.9273255813953488,"width":0.7798165137614679,"height":0.055232558139534885,"font_size":16,"text":"So does a handgun."}]}'
end
let (:submit_hash_example) do
JSON.parse(submit_json_example)
end
# definitions to be overriden in specific contexts (by doing a clone of the
# '_example' counterpart and changing what is needed)
let (:ballonizer_settings) { ballonizer_settings_example }
let (:ballonizer_new_args) { ballonizer_new_args_example }
let (:env) { env_example }
let (:original_page) { original_page_example.to_s }
let (:page_url) { page_url_example }
let (:submit_json) { submit_json_example }
let (:submit_hash) { submit_hash_example }
# Definition who need others (no *_example)
let (:instance) { described_class.new(*ballonizer_new_args) }
let (:ballonize_page_call) do
lambda { instance.ballonize_page(
original_page, page_url, mime_type, ballonize_page_settings_arg
)}
end
let (:ballonized_page) do
ballonize_page_call.call
end
# TODO: verify if the style property has the correct values
describe '#ballonize_page' do
subject { ballonized_page }
context "when the mime-type isn't valid" do
let (:mime_type) { 'a invalid mime-type' }
it { expect(ballonize_page_call).to raise_error(Ballonizer::Error) }
end
context "when the mime-type is valid" do
context '(text/html)' do
let (:mime_type) { 'text/html; charset=utf8' }
it { expect(ballonize_page_call).to_not raise_error }
end
context '(application/xhtml+xml)' do
let (:mime_type) { 'application/xhtml+xml; charset=utf8' }
it { expect(ballonize_page_call).to_not raise_error }
end
end
context "when the mime-type is 'application/xhtml+xml'" do
context "but the page isn't a xml" do
let (:original_page) do
<<-END
A title
END
end
it 'return the original (not cloned) page argument, unmodified' do
expect(ballonized_page).to be(original_page)
end
end
end
context 'when the page has no img elements to ballonize' do
it "don't make changes in the page" do
should be_equivalent_to(original_page)
end
end
context 'when the page has img elements to ballonize' do
context 'and one of them have .to_ballonize class' do
let (:original_page) do
page_with_images = original_page_example.clone
img1 = ""
img2 = ""
page_with_images.at_css('body') << img1 << img2
page_with_images.to_s
end
it 'add a container around the img' do
should have_tag('span', :with => { class: 'ballonizer_image_container' }) do
with_tag('img', :with => { alt: 'A test image' })
end
end
context 'and it have ballons in the database' do
it 'add the ballons inside the container' do
# the parentheses of the 'should' are necessary, otherwise the
# conditions inside the block are silently not tested
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
with_tag("img[alt='A test image']")
with_tag('span', { text: 'first ballon of first image',
with: { class: 'ballonizer_ballon' }})
with_tag('span', { text: 'second ballon of first image',
with: { class: 'ballonizer_ballon'}})
end)
end
end
context 'and the settings define to insert css' do
let (:ballonizer_settings) do
ballonizer_settings_example.merge({
add_required_css: true,
css_asset_path_for_link: '/assets/css/'
})
end
it 'insert the link tag (with the correct path)' do
# the example html don't have any link tag, so if there one
# it was added by the method
should have_tag('link', :with => { type: 'text/css' })
# we are using xhtml, so we parse as xml
link_href = Nokogiri::XML(subject).at_css('link').attr('href')
expect(link_href).to match(/^\/assets\/css\//)
end
context 'but the settings argument override the path' do
let (:ballonize_page_settings_arg) do
{ css_asset_path_for_link: '/other_assets/css/' }
end
it 'use the settings argument path' do
# we are using xhtml, so we parse as xml
link_href = Nokogiri::XML(subject).at_css('link').attr('href')
expect(link_href).to match(/^\/other_assets\/css\//)
end
end
end
context 'and the settings define to insert js' do
let (:ballonizer_settings) do
ballonizer_settings_example.merge({
# this option is added to avoid a false positive in the test
add_js_for_edition: false,
add_required_js_libs_for_edition: true,
js_asset_path_for_link: '/assets/js/'
})
end
it 'insert the script tag (with the correct src)' do
should have_tag('script', :with => { type: 'text/javascript' })
# we are using xhtml, so we parse as xml
script_src = Nokogiri::XML(subject).at_css('script').attr('src')
expect(script_src).to match(/^\/assets\/js\//)
end
context 'but the settings argument override the path' do
let (:ballonize_page_settings_arg) do
{ js_asset_path_for_link: '/other_assets/js/' }
end
it 'use the settings argument path' do
# we are using xhtml, so we parse as xml
script_src = Nokogiri::XML(subject).at_css('script').attr('src')
expect(script_src).to match(/^\/other_assets\/js\//)
end
end
end
context 'and the settings argument override the form_handler_url' do
let (:ballonize_page_settings_arg) do
{ form_handler_url: '/other_request_handler',
add_js_for_edition: true,
add_required_js_libs_for_edition: false
}
end
it 'change the js snippet to use the argument one' do
# we are using xhtml, so we parse as xml
script_text = Nokogiri::XML(subject).at_css('script').text
expect(script_text).to match(/\/other_request_handler/)
end
end
end
context 'and more than one of them is to be ballonized' do
let (:original_page) do
page_with_images = original_page_example.clone
# the first image in the db is used in the other test, don't need to
# be reused
img2 = ""
img3 = ""
img4 = ""
page_with_images.at_css('body') << img2 << img3 << img4
page_with_images.to_s
end
it 'add a container around the imgs' do
# TODO: DRY this test
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
with_tag('img', :with => { alt: 'the second test image' })
end)
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
with_tag('img', :with => { alt: 'the third test image' })
end)
end
context 'and they have ballons in the database' do
it 'add the ballons inside the containers' do
# TODO: break this spec in smaller parts, this specificate more
# than one thing
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
# the second image have two versions, the second ballon is added
# in the second version, so here we verify if the ballonize_page
# recover the ballons of the last version
with_tag('img', :with => { alt: 'the second test image' })
with_tag('span', { text: 'first ballon of second image',
with: { class: 'ballonizer_ballon' }})
with_tag('span', { text: 'second ballon of second image',
with: { class: 'ballonizer_ballon' }})
end)
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
# the third image have two versions, the second ballon is removed
# in the second version, so here we verify if the ballonize_page
# do not use a ballon of an old version in the image
with_tag('img', :with => { alt: 'the third test image' })
with_tag('span', { text: 'first ballon of third image',
with: { class: 'ballonizer_ballon' }})
without_tag('span', { text: 'second ballon of third image',
with: { class: 'ballonizer_ballon' }})
end)
end
end
end
context 'and no one of them have the .to_ballonize class' do
let (:original_page) do
page_with_images = original_page_example.clone
page_with_images.at_css('body')
.add_child ""
page_with_images.to_s
end
it "don't add a container around the imgs" do
should_not have_tag('span', :with => {
class: 'ballonizer_image_container'
})
end
it "don't add the links of the required javascript and css" do
# the callback will be called only if a image was ballonized?
should_not have_tag('link')
end
end
end
end
describe '#valid_submit_json?' do
subject { instance.valid_submit_json? submit_json }
shared_examples 'common behavior for invalid input' do
it { should be_false }
it 'taint the input' do
instance.valid_submit_json? submit_json
expect(submit_json.tainted?).to be_true
end
context 'but frozen' do
let (:frozen_submit_json) { deep_copy(submit_json).freeze }
let (:method_call) do
lambda { instance.valid_submit_json? submit_json }
end
it "shouldn't taint the input" do
# an attempt to taint a frozen object will raise a RuntimeError
expect(method_call).to_not raise_error
end
end
context 'when the second argument is true' do
let (:method_call) do
lambda { instance.valid_submit_json?(submit_json, true) }
end
# necessary define the 'exception_type' let variable in the context
# who will use this shared_examples
it { expect(method_call).to raise_error(exception_type) }
end
end
# NOTE: All cases who throw a Ballonizer::SubmitError are verified
# in the #valid_submit_hash? (who is used by the #valid_submit_json?)
context 'with invalid input' do
context '(a malformed json)' do
let (:submit_json) { 'not a valid json' }
let (:exception_type) { JSON::ParserError }
include_examples 'common behavior for invalid input'
end
context '(invalid submit data)' do
let (:submit_json) { '{}' }
let (:exception_type) { described_class::SubmitError }
include_examples 'common behavior for invalid input'
end
end
context 'with valid input' do
it { should be_true }
it 'untaint the input' do
instance.valid_submit_json? submit_json.taint
expect(submit_json.tainted?).to be_false
end
context 'but frozen' do
let (:frozen_tainted_submit_json) do
deep_copy(submit_json).taint.freeze
end
let (:method_call) do
lambda { instance.valid_submit_json? submit_json }
end
it "shouldn't untaint the input" do
# an attempt to taint a frozen object will raise a RuntimeError
expect(method_call).to_not raise_error
end
end
context 'when the second argument is true' do
let (:method_call) do
lambda { instance.valid_submit_json?(submit_json, true) }
end
it { expect(method_call).to_not raise_error }
end
end
end
# TODO: validate ballon text and url size < 255; if the url is a url;
# extra fields in the hash?
describe '#valid_submit_hash?' do
subject { instance.valid_submit_hash? submit_hash }
# To be used in all contexts where the input is invalid
shared_examples 'and the second argument is true' do
# If this context isn't added the message don't appear in the output
# of the 'rspec -fd' and the lets and subjects are merged (what can
# create some nasty bugs)
context 'and the second argument is true' do
let (:method_call) do
lambda { instance.valid_submit_hash?(submit_hash, true) }
end
it { expect(method_call).to raise_error(described_class::SubmitError) }
end
end
context "when the bounds aren't numbers between 0 and 1" do
let (:submit_hash) do
deep_copy(submit_hash_example)[submit_hash_example.keys.first]
.first.update({ top: 10 })
end
it { should be_false }
include_examples 'and the second argument is true'
end
context "when the text isn't a non-empty String" do
let (:submit_hash) do
deep_copy(submit_hash_example)[submit_hash_example.keys.first]
.first.update({ text: "" })
end
it { should be_false }
include_examples 'and the second argument is true'
end
[:x, :y].each do | axis |
position, size = { x: ["left", "top"], y: ["top", "height"] }[axis]
context "when the #{position} plus #{size} is greater than one" do
let (:submit_hash) do
h = deep_copy(submit_hash_example)
first_ballon = h[h.keys.first].first
first_ballon[position] = 0.75
first_ballon[size] = 0.5
h
end
it { should be_false }
include_examples 'and the second argument is true'
end
end
context "when a ballon don't have font_size" do
let (:submit_hash) do
deep_copy(submit_hash_example)[submit_hash_example.keys.first]
.first.update({ font_size: nil })
end
it { should be_false }
include_examples 'and the second argument is true'
end
context 'when the submit contain a image without ballons' do
let (:submit_hash) do
{ submit_hash_example.keys.first => [] }
end
it { should be_true }
end
context 'when the hash is valid' do
it { should be_true }
context 'and the second argument is true' do
let (:method_call) do
lambda { instance.valid_submit_hash?(submit_hash, true) }
end
it { expect(method_call).to_not raise_error }
it { expect(method_call.call).to be_true }
end
end
end
describe '#process_submit_hash' do
# TODO: Add context "when the submit refer to two or more images?"
context 'when its the first submit of a image' do
let(:submit_hash) do
# the keys are String because hashs parsed from JSON are this way
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/4.jpg' => [
{ 'text' => 'the first ballon of the fourth image',
'left' => 0, 'top' => 0, 'width' => 0.5, 'height' => 0.5 },
{ 'text' => 'the second ballon of the fourth image',
'left' => 0.5, 'top' => 0.5, 'width' => 0.5, 'height' => 0.5 },
]}
end
let (:original_page) do
page_with_images = original_page_example.clone
img = ""
page_with_images.at_css('body') << img
page_with_images.to_s
end
it 'the ballonize_page add the ballons to the image' do
instance.process_submit_hash(submit_hash, Time.at(0))
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
end
end
context 'when the submit refer to a image already with ballons' do
let(:submit_hash) do
# the keys are String because hashs parsed from JSON are this way
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/1.jpg' => [
# the "(... version)" added to avoid reutilize a ballon (what is
# not the objective of the test here)
{ 'text' => 'the first ballon (version 2) of the first image',
'left' => 0, 'top' => 0, 'width' => 0.5, 'height' => 0.5 },
{ 'text' => 'the second ballon (version 2) of the first image',
'left' => 0.5, 'top' => 0.5, 'width' => 0.5, 'height' => 0.5 },
]}
end
let (:original_page) do
page_with_images = original_page_example.clone
img = ""
page_with_images.at_css('body') << img
page_with_images.to_s
end
it 'the ballonize_page use the new ballons' do
instance.process_submit_hash(submit_hash, Time.at(0))
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
end
end
end
describe '#process_submit_json' do
# As process_submit_json use process_submit_hash (it's only a convenience
# method) we don't test the rest of its behaviour (who is already covered
# in #process_submit_hash)
context 'when the input is tainted' do
let (:submit_json) do
submit_hash_example.clone.taint
end
it do
expect {
instance.process_submit_json(submit_json)
}.to raise_error(SecurityError)
end
end
end
describe '#process_submit' do
# As process_submit use the #valid_submit_json? and #process_submit_hash
# we only make the tests here for example and its not the ideia cover the
# cases already covered in the specs of the two methods
context 'when the input is invalid' do
# The submit hash is used to define the env_example
# (and in consequence the env)
let (:submit_hash) do
# this input is invalid because have no font_size
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/4.jpg' => [
{ 'text' => 'test', 'left' => 0, 'top' => 0, 'width' => 0.5,
'height' => 0.5 }
]}
end
it do
expect { instance.process_submit(env) }.to raise_error(
described_class::SubmitError
)
end
end
context 'when the input is valid' do
# this code is almost the same as the used in #process_submit_hash,
# think in some way to DRY up this
let(:submit_hash) do
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/4.jpg' => [
{ 'text' => 'the first ballon of the fourth image',
'left' => 0, 'top' => 0, 'width' => 0.5, 'height' => 0.5,
'font_size' => 16 }
]}
end
let (:original_page) do
page_with_images = original_page_example.clone
img = ""
page_with_images.at_css('body') << img
page_with_images.to_s
end
it 'the ballonize_page add the ballons to the image' do
instance.process_submit(env)
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
end
end
end
describe '#js_load_snippet' do
subject { instance.js_load_snippet(settings_arg) }
let (:settings_arg) { Hash.new }
let (:ballonizer_settings) do
ballonizer_settings_example.merge({
form_handler_url: '/request_handler'
})
end
it 'return contains the :form_handler_url setting' do
should match(instance.settings[:form_handler_url])
end
context 'when the settings argument is passed' do
let (:settings_arg) do
{ form_handler_url: '/other_request_handler' }
end
it 'use the argument one' do
should match(settings_arg[:form_handler_url])
should_not match(instance.settings[:form_handler_url])
end
end
end
describe '#css_html_links' do
shared_examples 'using settings parameter' do
context 'and a path is passed by the settings hash argument' do
it 'use the path from the argument' do
css_links = instance.css_html_links({
css_asset_path_for_link: '/other_assets/css/'
})
REXML::Document.new("#{css_links}")
.root.children.each do | e |
expect(e.attributes['href']).to match(/^\/other_assets\/css\//)
end
end
end
end
context "when the path for the css isn't configured" do
it 'returns nil' do
expect(instance.css_html_links).to eq nil
end
include_examples 'using settings parameter'
end
context 'when the path for the css is configured' do
let (:ballonizer_settings) do
ballonizer_settings_example.merge({
css_asset_path_for_link: '/assets/css/'
})
end
it 'return a HTML string with links prefixed by the path' do
REXML::Document.new("#{instance.css_html_links}")
.root.children.each do | e |
expect(e.attributes['href']).to match(/^\/assets\/css\//)
end
end
include_examples 'using settings parameter'
end
end
describe '#js_libs_html_links' do
shared_examples 'using settings parameter' do
context 'and a path is passed by the settings hash argument' do
it 'use the path from the argument' do
js_scripts = instance.js_libs_html_links({
js_asset_path_for_link: '/other_assets/js/'
})
REXML::Document.new("#{js_scripts}")
.root.children.each do | e |
expect(e.attributes['src']).to match(/^\/other_assets\/js\//)
end
end
end
end
context "when the path for the js libs isn't configured" do
it 'returns nil' do
expect(instance.js_libs_html_links).to eq nil
end
include_examples 'using settings parameter'
end
context 'when the path for the js libs is configured' do
let (:ballonizer_settings) do
ballonizer_settings_example.merge({
js_asset_path_for_link: '/assets/js/'
})
end
it 'return a HTML string with links prefixed by the path' do
REXML::Document.new("#{instance.js_libs_html_links}")
.root.children.each do | e |
expect(e.attributes['src']).to match(/^\/assets\/js\//)
end
end
include_examples 'using settings parameter'
end
end
describe '.asset_load_paths' do
it 'return an array of paths who exist in the gem root dir' do
# TODO: change to __dir__ when the 2.0 become widely adopted
spec_dir = File.dirname(File.realpath(__FILE__))
gem_root_dir = File.expand_path('../', spec_dir)
absolute_load_paths = described_class.asset_load_paths.map do | path |
File.expand_path(path, gem_root_dir)
end
expect(absolute_load_paths).to exist_in_filesystem
end
end
describe '.asset_absolute_paths' do
it 'return an array of absolute filepaths who exist' do
expect(described_class.asset_absolute_paths).to exist_in_filesystem
end
end
describe '.asset_logical_paths' do
it 'return the last part of the asset_absolute_paths' do
logical_paths = described_class.asset_logical_paths
absolute_paths = described_class.asset_absolute_paths
are_last_part_of_absolute = logical_paths.all? do | lp |
absolute_paths.any? { | ap | ap.end_with? lp }
end
expect(are_last_part_of_absolute).to be_true
end
end
describe '.assets_app' do
let (:envs) do
described_class.asset_logical_paths.map do | asset_logical_path |
env_example.merge({
'HTTP_HOST' => 'example.net',
'SERVER_NAME' => 'example.net',
'PATH_INFO' => asset_logical_path,
'REQUEST_METHOD' => 'GET',
'rack.input' => nil,
'CONTENT_TYPE' => nil,
'CONTENT_LENGTH' => nil
})
end
end
it 'provide the css and js libs required by the gem' do
envs.each do | env |
expect(described_class.assets_app.call(env)[0]).to eq 200
end
end
end
end