ballonizer 0.2.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/ballonizer_app/config.ru +4 -2
- data/examples/ballonizer_app/index.html +105 -157
- data/examples/ballonizer_app/test.db +0 -0
- data/examples/ballonizer_js_module/index.html +4 -4
- data/lib/assets/javascripts/ballonizer.js +119 -17
- data/lib/assets/stylesheets/ballonizer.css +17 -12
- data/lib/ballonizer.rb +81 -25
- data/spec/ballonizer_spec.rb +108 -45
- data/spec/javascripts/ballonizer_spec.js +186 -129
- data/spec/javascripts/fixtures/ballonized-xkcd-with-anchor-in-image.html +2 -2
- data/spec/javascripts/fixtures/ballonized-xkcd-with-ballons.html +2 -2
- data/spec/javascripts/fixtures/ballonized-xkcd-without-ballons.html +2 -2
- metadata +3 -2
@@ -14,7 +14,9 @@
|
|
14
14
|
background-color: white;
|
15
15
|
color: black;
|
16
16
|
text-align: center;
|
17
|
-
|
17
|
+
display: block;
|
18
|
+
word-wrap: break-word;
|
19
|
+
overflow: hidden;
|
18
20
|
}
|
19
21
|
|
20
22
|
.ballonizer_ballon_hidden_for_edition
|
@@ -31,44 +33,47 @@
|
|
31
33
|
* occupy space (no one). With this the form is "hidden" because don't occupy
|
32
34
|
* any space (the width AND height are equal zero).
|
33
35
|
*/
|
34
|
-
.ballonizer_image_form
|
36
|
+
.ballonizer_image_form, .ballonizer_image_form div
|
35
37
|
{
|
36
38
|
margin: 0;
|
37
39
|
padding: 0;
|
38
40
|
border: 0;
|
41
|
+
left: 0;
|
42
|
+
top: 0;
|
39
43
|
display: inline-block;
|
44
|
+
/* we must */
|
45
|
+
position: relative;
|
40
46
|
}
|
41
47
|
|
42
|
-
.ballonizer_image_form
|
48
|
+
.ballonizer_image_form .ballonizer_edition_ballon
|
43
49
|
{
|
44
50
|
position: absolute;
|
45
51
|
display: none;
|
52
|
+
border: 0;
|
46
53
|
margin: 0;
|
47
54
|
text-align: center;
|
48
55
|
}
|
49
56
|
|
50
|
-
.ballonizer_image_form
|
57
|
+
.ballonizer_image_form .ballonizer_edition_ballon.ballonizer_ballon_in_edition
|
51
58
|
{
|
52
59
|
display: block;
|
53
60
|
}
|
54
61
|
|
55
62
|
/* We use the same trick used in the image form in the page form */
|
56
|
-
.ballonizer_page_form
|
63
|
+
.ballonizer_page_form, .ballonizer_page_form div
|
57
64
|
{
|
58
65
|
display: inline-block;
|
59
|
-
padding: 0;
|
60
66
|
margin: 0;
|
61
|
-
|
67
|
+
padding: 0;
|
68
|
+
border: 0;
|
62
69
|
}
|
63
70
|
|
64
|
-
.ballonizer_page_form
|
71
|
+
.ballonizer_page_form input[type=submit]
|
65
72
|
{
|
66
|
-
|
67
|
-
margin: 0;
|
68
|
-
padding: 0;
|
73
|
+
display: none;
|
69
74
|
}
|
70
75
|
|
71
|
-
.ballonizer_page_form
|
76
|
+
.ballonizer_page_form input[type=submit].ballonizer_ballons_have_changes
|
72
77
|
{
|
73
78
|
display: block;
|
74
79
|
position: fixed;
|
data/lib/ballonizer.rb
CHANGED
@@ -54,6 +54,24 @@ require 'sprockets'
|
|
54
54
|
# The tables names are: images, ballons, ballonized_image_versions,
|
55
55
|
# ballonized_image_ballons.
|
56
56
|
#
|
57
|
+
# Changelog:
|
58
|
+
# v0.4.0:
|
59
|
+
# * Changed the way the Javascript module add containers in the page
|
60
|
+
# to avoid creating invalid HTML4.0.1/XHTML1.1/HTML5 documents.
|
61
|
+
# * Now the ballonize_page takes a mime-type argument to decide if
|
62
|
+
# the page has to be parsed as XML or HTML (trying to be in
|
63
|
+
# conformance with http://www.w3.org/TR/xhtml-media-types/).
|
64
|
+
# * The change in the ballon size now change the font-size of the
|
65
|
+
# ballon text.
|
66
|
+
# * Database schema change, as consequence of the font-size change,
|
67
|
+
# the database now stores the font-size. No migration provided for
|
68
|
+
# databases in the old format, but the font-size field can be null.
|
69
|
+
# The migration only require adding this column with null value to
|
70
|
+
# all records (see the create_tables code).
|
71
|
+
# * Fixed a bug in the Javascript module that give wrong position and
|
72
|
+
# size values to all ballons that aren't edited/added before submmiting
|
73
|
+
# (only if the image wasn't loaded before the javascript loading).
|
74
|
+
#
|
57
75
|
class Ballonizer
|
58
76
|
|
59
77
|
# The superclass of any error explicitly raised by the Ballonizer class.
|
@@ -75,22 +93,38 @@ class Ballonizer
|
|
75
93
|
e.freeze
|
76
94
|
end
|
77
95
|
|
78
|
-
def self.parse_html_or_xhtml(doc)
|
79
|
-
# If you parse XHTML as HTML with Nokogiri and use to_s after the markup
|
96
|
+
def self.parse_html_or_xhtml(doc, mime_type)
|
97
|
+
# If you parse XHTML as HTML with Nokogiri, and use to_s after, the markup
|
98
|
+
# can be messed up, breaking the structural integrity of the xml
|
80
99
|
#
|
81
100
|
# Example: <meta name="description" content="not important" />
|
82
101
|
# becomes <meta name="description" content="not important" >
|
83
|
-
#
|
102
|
+
#
|
103
|
+
# In the other side if you parse HTML as a XML, and use to_s after, the
|
104
|
+
# Nokogiri make empty content tags self-close
|
105
|
+
#
|
106
|
+
# Example: <script type="text/javascript" src="/ballonizer.js"></script>
|
107
|
+
# becomes: <script type="text/javascript" src="/ballonizer.js" />
|
108
|
+
#
|
109
|
+
# What's even worse than the contrary (xml as html)
|
84
110
|
parsed_doc = nil
|
85
|
-
|
86
|
-
|
87
|
-
|
111
|
+
|
112
|
+
case mime_type
|
113
|
+
when /text\/html/
|
114
|
+
parsed_doc = Nokogiri::HTML(doc)
|
115
|
+
when /application\/xhtml\+xml/
|
88
116
|
options = Nokogiri::XML::ParseOptions::DEFAULT_XML &
|
89
117
|
Nokogiri::XML::ParseOptions::STRICT &
|
90
118
|
Nokogiri::XML::ParseOptions::NONET
|
91
|
-
|
92
|
-
|
93
|
-
|
119
|
+
begin
|
120
|
+
parsed_doc = Nokogiri::XML::Document.parse(doc, nil, nil, options)
|
121
|
+
rescue
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
else
|
125
|
+
fail Error, "the only mime-types accepted are text/html and" +
|
126
|
+
" application/xhtml+xml, the passed argument was " +
|
127
|
+
"'#{mime_type}'"
|
94
128
|
end
|
95
129
|
|
96
130
|
parsed_doc
|
@@ -258,12 +292,16 @@ class Ballonizer
|
|
258
292
|
if ballon["text"].empty?
|
259
293
|
fail SubmitError, "the ballon text is empty"
|
260
294
|
end
|
295
|
+
[:top, :left, :width, :height, :font_size].each do | numeric_attr_name |
|
296
|
+
numeric_attr = ballon[numeric_attr_name.to_s]
|
297
|
+
unless numeric_attr.is_a?(Fixnum) || numeric_attr.is_a?(Float)
|
298
|
+
fail SubmitError, "the #{numeric_attr_name} " +
|
299
|
+
"(#{numeric_attr}) isn't a Fixnum or " +
|
300
|
+
"Float (is a '#{numeric_attr.class}')"
|
301
|
+
end
|
302
|
+
end
|
261
303
|
[:top, :left, :width, :height].each do | bound_name |
|
262
304
|
bound = ballon[bound_name.to_s]
|
263
|
-
unless bound.is_a?(Fixnum) || bound.is_a?(Float)
|
264
|
-
fail SubmitError, "the #{bound_name.to_s} (#{bound.to_s}) isn't" +
|
265
|
-
" a Fixnum or Float (is a '#{bound.class.to_s}')"
|
266
|
-
end
|
267
305
|
unless bound >= 0 && bound <= 1
|
268
306
|
fail SubmitError, "the #{bound_name.to_s} (#{bound.to_s}) isn't"
|
269
307
|
" between 0 and 1 (both inclusive)"
|
@@ -271,14 +309,14 @@ class Ballonizer
|
|
271
309
|
end
|
272
310
|
|
273
311
|
ballon_end = {}
|
274
|
-
ballon_end[:x] = ballon["
|
275
|
-
ballon_end[:y] = ballon["
|
312
|
+
ballon_end[:x] = ballon["left"] + ballon["width"]
|
313
|
+
ballon_end[:y] = ballon["top"] + ballon["height"]
|
276
314
|
|
277
315
|
[:x, :y].each do | axis |
|
278
316
|
if ballon_end[axis] > 1
|
279
|
-
|
280
|
-
|
281
|
-
" of the image"
|
317
|
+
side = { x: "right side", y: "bottom" }[axis]
|
318
|
+
fail SubmitError, "the ballon with text #{ballon["text"].to_s} " +
|
319
|
+
"is trespassing the #{side} of the image"
|
282
320
|
end
|
283
321
|
end
|
284
322
|
end
|
@@ -351,22 +389,35 @@ class Ballonizer
|
|
351
389
|
# Wrap each image to ballonize with a container, add its ballons to the
|
352
390
|
# container and, possibly, add the css and js libs and snippet for the
|
353
391
|
# edition initialization. Don't make any change if the page has no images
|
354
|
-
# to ballonize.
|
392
|
+
# to ballonize. If the page can't be parsed (as HTML or X(HT)ML, depending
|
393
|
+
# of the mime-type) return the page argument without throwing any exceptions.
|
394
|
+
# Throw an exception if the mime-type doesn't match with html or xhtml.
|
355
395
|
# @param page [String] The (X)HTML page.
|
356
396
|
# @param page_url [String] The url of the page to be ballonized, necessary
|
357
397
|
# to make absolute the src attribute of img (if it's relative).
|
358
398
|
# @param settings [Hash{Symbol => String}] Optional. If not provided the
|
359
399
|
# #settings will be used.
|
360
|
-
# @
|
361
|
-
|
400
|
+
# @param mime_type A string that have the substring 'text/html' or
|
401
|
+
# 'application/xhtml+xml'.
|
402
|
+
# @return [String] The ballonized page (new string), or the same string,
|
403
|
+
# if the parse has failed.
|
404
|
+
# @raise [Ballonizer::Error] If the mime-type don't match either 'text/html'
|
405
|
+
# or 'application/xhtml+xml'.
|
406
|
+
def ballonize_page(page, page_url, mime_type, settings = {})
|
362
407
|
settings = self.settings.merge(settings)
|
363
408
|
|
364
|
-
|
409
|
+
# can raise Ballonizer::Error if the mime-type is invalid
|
410
|
+
parsed_page = Workaround.parse_html_or_xhtml(page, mime_type)
|
411
|
+
# if can't parse return the page unaltered
|
412
|
+
if parsed_page.nil?
|
413
|
+
return page
|
414
|
+
end
|
415
|
+
|
365
416
|
selector = settings[:img_to_ballonize_css_selector]
|
366
417
|
imgs = parsed_page.css(selector)
|
367
418
|
|
368
419
|
unless imgs.empty?
|
369
|
-
imgs.wrap('<
|
420
|
+
imgs.wrap('<span class="ballonizer_image_container" ></span>')
|
370
421
|
|
371
422
|
imgs.each do | img |
|
372
423
|
img_src = img['src']
|
@@ -403,8 +454,9 @@ class Ballonizer
|
|
403
454
|
# transform ratio [0,1] to percent [0, 100]
|
404
455
|
style = style + "#{sym}: #{(ballon_data[sym] * 100)}%;"
|
405
456
|
end
|
457
|
+
style = style + "font-size: #{ballon_data[:font_size]}px;"
|
406
458
|
|
407
|
-
"<
|
459
|
+
"<span class='ballonizer_ballon' style='#{style}'>#{text}</span>"
|
408
460
|
end
|
409
461
|
|
410
462
|
# @api private
|
@@ -422,7 +474,8 @@ class Ballonizer
|
|
422
474
|
.join(:ballons, { ballonized_image_ballons__version: version,
|
423
475
|
ballonized_image_ballons__image_id: image_id,
|
424
476
|
ballonized_image_ballons__ballon_id: :ballons__id
|
425
|
-
}).select(:text, :top, :left, :width, :height
|
477
|
+
}).select(:text, :top, :left, :width, :height,
|
478
|
+
:font_size).all
|
426
479
|
else
|
427
480
|
[]
|
428
481
|
end
|
@@ -458,6 +511,9 @@ class Ballonizer
|
|
458
511
|
Float :left, :allow_null => false
|
459
512
|
Float :width, :allow_null => false
|
460
513
|
Float :height, :allow_null => false
|
514
|
+
# the font_size allow null to support databases migrated from old versions
|
515
|
+
# (that don't have this field)
|
516
|
+
Float :font_size, :allow_null => true
|
461
517
|
end
|
462
518
|
db.create_table(:ballonized_image_versions) do
|
463
519
|
Integer :version
|
data/spec/ballonizer_spec.rb
CHANGED
@@ -31,6 +31,24 @@ RSpec::Matchers.define :exist_in_filesystem do
|
|
31
31
|
"be a list of absolute paths for existing files or directories"
|
32
32
|
end
|
33
33
|
end
|
34
|
+
# TODO: check the font-size, the position and the size of the ballon
|
35
|
+
# TODO: check the src of the image inside the container (not so easy
|
36
|
+
# because the src is relative in the html but absolute in the database)
|
37
|
+
RSpec::Matchers.define :have_ballons_as_submitted_by do | expected |
|
38
|
+
match do | actual |
|
39
|
+
expected.each do | img_src, ballons |
|
40
|
+
expect(actual).to(have_tag('.ballonizer_image_container') do
|
41
|
+
with_tag('img')
|
42
|
+
ballons.each do | b |
|
43
|
+
with_tag('span', { text: b['text'], with: { class: 'ballonizer_ballon' }})
|
44
|
+
end
|
45
|
+
end)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
description do
|
49
|
+
'have ballons as the ones added by the last submit'
|
50
|
+
end
|
51
|
+
end
|
34
52
|
|
35
53
|
describe Ballonizer do
|
36
54
|
|
@@ -42,17 +60,23 @@ describe Ballonizer do
|
|
42
60
|
db[:images].insert({img_src: 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/3.jpg'})
|
43
61
|
|
44
62
|
db[:ballons].insert({ text: 'first ballon of first image',
|
45
|
-
top: 0, left: 0, width: 0.5, height: 0.5
|
63
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
64
|
+
font_size: 16 })
|
46
65
|
db[:ballons].insert({ text: 'second ballon of first image',
|
47
|
-
top: 0
|
66
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
67
|
+
font_size: 16 })
|
48
68
|
db[:ballons].insert({ text: 'first ballon of second image',
|
49
|
-
top: 0, left: 0, width: 0.5, height: 0.5
|
69
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
70
|
+
font_size: 16 })
|
50
71
|
db[:ballons].insert({ text: 'second ballon of second image',
|
51
|
-
top: 0
|
72
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
73
|
+
font_size: 16 })
|
52
74
|
db[:ballons].insert({ text: 'first ballon of third image',
|
53
|
-
top: 0, left: 0, width: 0.5, height: 0.5
|
75
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
76
|
+
font_size: 16 })
|
54
77
|
db[:ballons].insert({ text: 'second ballon of third image',
|
55
|
-
top: 0
|
78
|
+
top: 0, left: 0, width: 0.5, height: 0.5,
|
79
|
+
font_size: 16 })
|
56
80
|
|
57
81
|
# both ballons added in the first version
|
58
82
|
db[:ballonized_image_versions].insert({image_id: 1, version: 1, time: time})
|
@@ -81,6 +105,10 @@ describe Ballonizer do
|
|
81
105
|
JSON.parse(JSON.generate(v))
|
82
106
|
end
|
83
107
|
|
108
|
+
# definitions to be overriden in need, but who doesn't need a *_example
|
109
|
+
# counterpart (are so simple that clone and change isn't pratical)
|
110
|
+
let (:mime_type) { 'application/xhtml+xml' }
|
111
|
+
|
84
112
|
# Definitions ending with '_example' are to be cloned and defined in a
|
85
113
|
# context without the sufix. Definitions without the sufix are used in the
|
86
114
|
# specs and may require the definition of some without '_example' counterparts.
|
@@ -139,7 +167,7 @@ describe Ballonizer do
|
|
139
167
|
{}
|
140
168
|
end
|
141
169
|
let (:submit_json_example) do
|
142
|
-
'{"http://imgs.xkcd.com/comics/cells.png":[{"left":0,"top":0,"width":1,"height":0.23837209302325582,"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,"text":"So does a handgun."}]}'
|
170
|
+
'{"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."}]}'
|
143
171
|
end
|
144
172
|
let (:submit_hash_example) do
|
145
173
|
JSON.parse(submit_json_example)
|
@@ -158,14 +186,53 @@ describe Ballonizer do
|
|
158
186
|
|
159
187
|
# Definition who need others (no *_example)
|
160
188
|
let (:instance) { described_class.new(*ballonizer_new_args) }
|
189
|
+
let (:ballonize_page_call) do
|
190
|
+
lambda { instance.ballonize_page(original_page, page_url, mime_type, settings) }
|
191
|
+
end
|
161
192
|
let (:ballonized_page) do
|
162
|
-
|
193
|
+
ballonize_page_call.call
|
163
194
|
end
|
164
195
|
|
165
196
|
# TODO: verify if the style property has the correct values
|
166
197
|
describe '#ballonize_page' do
|
167
198
|
subject { ballonized_page }
|
168
199
|
|
200
|
+
context "when the mime-type isn't valid" do
|
201
|
+
let (:mime_type) { 'a invalid mime-type' }
|
202
|
+
it { expect(ballonize_page_call).to raise_error(Ballonizer::Error) }
|
203
|
+
end
|
204
|
+
context "when the mime-type is valid" do
|
205
|
+
context '(text/html)' do
|
206
|
+
let (:mime_type) { 'text/html; charset=utf8' }
|
207
|
+
it { expect(ballonize_page_call).to_not raise_error }
|
208
|
+
end
|
209
|
+
context '(application/xhtml+xml)' do
|
210
|
+
let (:mime_type) { 'application/xhtml+xml; charset=utf8' }
|
211
|
+
it { expect(ballonize_page_call).to_not raise_error }
|
212
|
+
end
|
213
|
+
end
|
214
|
+
context "when the mime-type is 'application/xhtml+xml'" do
|
215
|
+
context "but the page isn't a xml" do
|
216
|
+
let (:original_page) do
|
217
|
+
<<-END
|
218
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
219
|
+
"http://www.w3.org/TR/html4/strict.dtd">
|
220
|
+
<html>
|
221
|
+
<head>
|
222
|
+
<title>A title</title>
|
223
|
+
<!-- this isn't a valid xml because the meta tag isn't closed -->
|
224
|
+
<meta http-equiv="content-type" content="text/html" charset=UTF-8">
|
225
|
+
</head>
|
226
|
+
<body>
|
227
|
+
</body>
|
228
|
+
</html>
|
229
|
+
END
|
230
|
+
end
|
231
|
+
it 'return the original (not cloned) page argument, unmodified' do
|
232
|
+
expect(ballonized_page).to be(original_page)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
169
236
|
context 'when the page has no img elements to ballonize' do
|
170
237
|
it "don't make changes in the page" do
|
171
238
|
should be_equivalent_to(original_page)
|
@@ -181,7 +248,7 @@ describe Ballonizer do
|
|
181
248
|
page_with_images.to_s
|
182
249
|
end
|
183
250
|
it 'add a container around the img' do
|
184
|
-
should have_tag('
|
251
|
+
should have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
185
252
|
with_tag('img', :with => { alt: 'A test image' })
|
186
253
|
end
|
187
254
|
end
|
@@ -189,16 +256,15 @@ describe Ballonizer do
|
|
189
256
|
it 'add the ballons inside the container' do
|
190
257
|
# the parentheses of the 'should' are necessary, otherwise the
|
191
258
|
# conditions inside the block are silently not tested
|
192
|
-
should(have_tag('
|
259
|
+
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
193
260
|
with_tag("img[alt='A test image']")
|
194
|
-
with_tag('
|
261
|
+
with_tag('span', { text: 'first ballon of first image',
|
195
262
|
with: { class: 'ballonizer_ballon' }})
|
196
|
-
with_tag('
|
263
|
+
with_tag('span', { text: 'second ballon of first image',
|
197
264
|
with: { class: 'ballonizer_ballon'}})
|
198
265
|
end)
|
199
266
|
end
|
200
267
|
end
|
201
|
-
# TODO: create this specs
|
202
268
|
context 'and the settings define to insert css' do
|
203
269
|
let (:ballonizer_settings) do
|
204
270
|
ballonizer_settings_example.merge({
|
@@ -241,10 +307,10 @@ describe Ballonizer do
|
|
241
307
|
end
|
242
308
|
it 'add a container around the imgs' do
|
243
309
|
# TODO: DRY this test
|
244
|
-
should(have_tag('
|
310
|
+
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
245
311
|
with_tag('img', :with => { alt: 'the second test image' })
|
246
312
|
end)
|
247
|
-
should(have_tag('
|
313
|
+
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
248
314
|
with_tag('img', :with => { alt: 'the third test image' })
|
249
315
|
end)
|
250
316
|
end
|
@@ -252,24 +318,24 @@ describe Ballonizer do
|
|
252
318
|
it 'add the ballons inside the containers' do
|
253
319
|
# TODO: break this spec in smaller parts, this specificate more
|
254
320
|
# than one thing
|
255
|
-
should(have_tag('
|
321
|
+
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
256
322
|
# the second image have two versions, the second ballon is added
|
257
323
|
# in the second version, so here we verify if the ballonize_page
|
258
324
|
# recover the ballons of the last version
|
259
325
|
with_tag('img', :with => { alt: 'the second test image' })
|
260
|
-
with_tag('
|
326
|
+
with_tag('span', { text: 'first ballon of second image',
|
261
327
|
with: { class: 'ballonizer_ballon' }})
|
262
|
-
with_tag('
|
328
|
+
with_tag('span', { text: 'second ballon of second image',
|
263
329
|
with: { class: 'ballonizer_ballon' }})
|
264
330
|
end)
|
265
|
-
should(have_tag('
|
331
|
+
should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
|
266
332
|
# the third image have two versions, the second ballon is removed
|
267
333
|
# in the second version, so here we verify if the ballonize_page
|
268
334
|
# do not use a ballon of an old version in the image
|
269
335
|
with_tag('img', :with => { alt: 'the third test image' })
|
270
|
-
with_tag('
|
336
|
+
with_tag('span', { text: 'first ballon of third image',
|
271
337
|
with: { class: 'ballonizer_ballon' }})
|
272
|
-
without_tag('
|
338
|
+
without_tag('span', { text: 'second ballon of third image',
|
273
339
|
with: { class: 'ballonizer_ballon' }})
|
274
340
|
end)
|
275
341
|
end
|
@@ -283,7 +349,7 @@ describe Ballonizer do
|
|
283
349
|
page_with_images.to_s
|
284
350
|
end
|
285
351
|
it "don't add a container around the imgs" do
|
286
|
-
should_not have_tag('
|
352
|
+
should_not have_tag('span', :with => {
|
287
353
|
class: 'ballonizer_image_container'
|
288
354
|
})
|
289
355
|
end
|
@@ -437,6 +503,17 @@ describe Ballonizer do
|
|
437
503
|
end
|
438
504
|
end
|
439
505
|
|
506
|
+
context "when a ballon don't have font_size" do
|
507
|
+
let (:submit_hash) do
|
508
|
+
deep_copy(submit_hash_example)[submit_hash_example.keys.first]
|
509
|
+
.first.update({ font_size: nil })
|
510
|
+
end
|
511
|
+
|
512
|
+
it { should be_false }
|
513
|
+
|
514
|
+
include_examples 'and the second argument is true'
|
515
|
+
end
|
516
|
+
|
440
517
|
context 'when the submit contain a image without ballons' do
|
441
518
|
let (:submit_hash) do
|
442
519
|
{ submit_hash_example.keys.first => [] }
|
@@ -444,6 +521,7 @@ describe Ballonizer do
|
|
444
521
|
|
445
522
|
it { should be_true }
|
446
523
|
end
|
524
|
+
|
447
525
|
context 'when the hash is valid' do
|
448
526
|
it { should be_true }
|
449
527
|
|
@@ -480,14 +558,7 @@ describe Ballonizer do
|
|
480
558
|
|
481
559
|
it 'the ballonize_page add the ballons to the image' do
|
482
560
|
instance.process_submit_hash(submit_hash, Time.at(0))
|
483
|
-
expect(ballonized_page).to
|
484
|
-
{ text: 'the first ballon of the fourth image',
|
485
|
-
with: { class: 'ballonizer_ballon' } }
|
486
|
-
)
|
487
|
-
expect(ballonized_page).to have_tag('p',
|
488
|
-
{ text: 'the second ballon of the fourth image',
|
489
|
-
with: { class: 'ballonizer_ballon' } }
|
490
|
-
)
|
561
|
+
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
|
491
562
|
end
|
492
563
|
end
|
493
564
|
context 'when the submit refer to a image already with ballons' do
|
@@ -512,14 +583,7 @@ describe Ballonizer do
|
|
512
583
|
|
513
584
|
it 'the ballonize_page use the new ballons' do
|
514
585
|
instance.process_submit_hash(submit_hash, Time.at(0))
|
515
|
-
expect(ballonized_page).to
|
516
|
-
{ text: 'the first ballon (version 2) of the first image',
|
517
|
-
with: { class: 'ballonizer_ballon' } }
|
518
|
-
)
|
519
|
-
expect(ballonized_page).to have_tag('p',
|
520
|
-
{ text: 'the second ballon (version 2) of the first image',
|
521
|
-
with: { class: 'ballonizer_ballon' } }
|
522
|
-
)
|
586
|
+
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
|
523
587
|
end
|
524
588
|
end
|
525
589
|
end
|
@@ -549,9 +613,10 @@ describe Ballonizer do
|
|
549
613
|
# The submit hash is used to define the env_example
|
550
614
|
# (and in consequence the env)
|
551
615
|
let (:submit_hash) do
|
552
|
-
# this input is invalid because
|
616
|
+
# this input is invalid because have no font_size
|
553
617
|
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/4.jpg' => [
|
554
|
-
{ 'text' => '', 'left' => 0, 'top' => 0, 'width' => 0.5,
|
618
|
+
{ 'text' => 'test', 'left' => 0, 'top' => 0, 'width' => 0.5,
|
619
|
+
'height' => 0.5 }
|
555
620
|
]}
|
556
621
|
end
|
557
622
|
|
@@ -568,7 +633,8 @@ describe Ballonizer do
|
|
568
633
|
let(:submit_hash) do
|
569
634
|
{ 'http://comic-translation.com/tr/pt-BR/a_comic/imgs/4.jpg' => [
|
570
635
|
{ 'text' => 'the first ballon of the fourth image',
|
571
|
-
'left' => 0, 'top' => 0, 'width' => 0.5, 'height' => 0.5
|
636
|
+
'left' => 0, 'top' => 0, 'width' => 0.5, 'height' => 0.5,
|
637
|
+
'font_size' => 16 }
|
572
638
|
]}
|
573
639
|
end
|
574
640
|
|
@@ -581,10 +647,7 @@ describe Ballonizer do
|
|
581
647
|
|
582
648
|
it 'the ballonize_page add the ballons to the image' do
|
583
649
|
instance.process_submit(env)
|
584
|
-
expect(ballonized_page).to
|
585
|
-
{ text: 'the first ballon of the fourth image',
|
586
|
-
with: { class: 'ballonizer_ballon' } }
|
587
|
-
)
|
650
|
+
expect(ballonized_page).to have_ballons_as_submitted_by(submit_hash)
|
588
651
|
end
|
589
652
|
end
|
590
653
|
end
|