ballonizer 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,9 @@
14
14
  background-color: white;
15
15
  color: black;
16
16
  text-align: center;
17
- overflow: hidden;
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 > .ballonizer_ballon_in_edition
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
- border: 0;
67
+ padding: 0;
68
+ border: 0;
62
69
  }
63
70
 
64
- .ballonizer_page_form > *
71
+ .ballonizer_page_form input[type=submit]
65
72
  {
66
- display: none;
67
- margin: 0;
68
- padding: 0;
73
+ display: none;
69
74
  }
70
75
 
71
- .ballonizer_page_form > .ballonizer_ballons_have_changes
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 can be messed up
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
- # To avoid this we parse a document that is XML valid as XML, and, otherwise as HTML
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
- begin
86
- # this also isn't a great way to do this
87
- # the Nokogiri don't have exception classes, this way any StandardError will be silenced
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
- parsed_doc = Nokogiri::XML::Document.parse(doc, nil, nil, options)
92
- rescue
93
- parsed_doc = Nokogiri::HTML(doc)
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["top"] + ballon["height"]
275
- ballon_end[:y] = ballon["left"] + ballon["width"]
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
- fail SubmitError, "the ballon with text #{ballon["text"]} is trespassing" +
280
- " the #{ {x: "right side", y: "bottom"} [axis] }" +
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
- # @return [String] The page ballonized (new string).
361
- def ballonize_page(page, page_url, settings = {})
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
- parsed_page = Workaround.parse_html_or_xhtml(page)
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('<div class="ballonizer_image_container" ></div>')
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
- "<p class='ballonizer_ballon' style='#{style}'>#{text}</p>"
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).all
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
@@ -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.5, left: 0.5, width: 0.5, height: 0.5 })
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.5, left: 0.5, width: 0.5, height: 0.5 })
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.5, left: 0.5, width: 0.5, height: 0.5 })
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
- instance.ballonize_page(original_page, page_url, settings)
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('div', :with => { class: 'ballonizer_image_container' }) do
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('div', :with => { class: 'ballonizer_image_container' }) do
259
+ should(have_tag('span', :with => { class: 'ballonizer_image_container' }) do
193
260
  with_tag("img[alt='A test image']")
194
- with_tag('p', { text: 'first ballon of first image',
261
+ with_tag('span', { text: 'first ballon of first image',
195
262
  with: { class: 'ballonizer_ballon' }})
196
- with_tag('p', { text: 'second ballon of first image',
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('div', :with => { class: 'ballonizer_image_container' }) do
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('div', :with => { class: 'ballonizer_image_container' }) do
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('div', :with => { class: 'ballonizer_image_container' }) do
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('p', { text: 'first ballon of second image',
326
+ with_tag('span', { text: 'first ballon of second image',
261
327
  with: { class: 'ballonizer_ballon' }})
262
- with_tag('p', { text: 'second ballon of second image',
328
+ with_tag('span', { text: 'second ballon of second image',
263
329
  with: { class: 'ballonizer_ballon' }})
264
330
  end)
265
- should(have_tag('div', :with => { class: 'ballonizer_image_container' }) do
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('p', { text: 'first ballon of third image',
336
+ with_tag('span', { text: 'first ballon of third image',
271
337
  with: { class: 'ballonizer_ballon' }})
272
- without_tag('p', { text: 'second ballon of third image',
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('div', :with => {
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 have_tag('p',
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 have_tag('p',
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 the text field can't be empty
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, 'height' => 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 have_tag('p',
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