ballonizer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/examples/ballonizer_app/config.ru +59 -0
  3. data/examples/ballonizer_app/index.html +159 -0
  4. data/examples/ballonizer_js_module/index.html +196 -0
  5. data/lib/assets/javascripts/ballonizer.js +482 -0
  6. data/lib/assets/stylesheets/ballonizer.css +78 -0
  7. data/lib/ballonizer.rb +201 -36
  8. data/spec/ballonizer_spec.rb +153 -2
  9. data/spec/javascripts/ballonizer_spec.js +568 -0
  10. data/spec/javascripts/fixtures/ballonized-xkcd-with-anchor-in-image.html +163 -0
  11. data/spec/javascripts/fixtures/ballonized-xkcd-with-ballons.html +163 -0
  12. data/spec/javascripts/fixtures/ballonized-xkcd-without-ballons.html +163 -0
  13. data/spec/javascripts/fixtures/xkcd.css +191 -0
  14. data/spec/javascripts/helpers/jasmine-jquery.js +660 -0
  15. data/spec/javascripts/helpers/jquery.simulate-ext.js +32 -0
  16. data/spec/javascripts/helpers/jquery.simulate.drag-n-drop.js +583 -0
  17. data/spec/javascripts/helpers/jquery.simulate.js +328 -0
  18. data/spec/javascripts/support/jasmine.yml +99 -0
  19. data/vendor/assets/javascripts/jquery-2.0.1.js +8837 -0
  20. data/vendor/assets/javascripts/jquery-ui-1.10.3.custom.min.js +6 -0
  21. data/vendor/assets/javascripts/jquery.json-2.4.min.js +24 -0
  22. data/vendor/assets/stylesheets/ui-lightness/images/animated-overlay.gif +0 -0
  23. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  24. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  25. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
  26. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  27. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  28. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  29. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  30. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  31. data/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  32. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
  33. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
  34. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
  35. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
  36. data/vendor/assets/stylesheets/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
  37. data/vendor/assets/stylesheets/ui-lightness/jquery-ui-1.10.3.custom.min.css +5 -0
  38. metadata +51 -3
data/lib/ballonizer.rb CHANGED
@@ -4,6 +4,7 @@ require 'sequel'
4
4
  require 'json'
5
5
  require 'htmlentities'
6
6
  require 'rack'
7
+ require 'sprockets'
7
8
 
8
9
  # This gem provides mechanisms to allow ballons (or speech bubbles) to be
9
10
  # added/removed/edited over images of a HTML or XHTML document and to be
@@ -16,7 +17,7 @@ require 'rack'
16
17
  #
17
18
  # This class lacks a lot of features like: access to an abstraction of the
18
19
  # ballons, images and their relationship; control over users who edit the
19
- # ballons; access to the old versions of the ballon set of a image (who
20
+ # ballons; access to the old versions of the ballon set of a image (that
20
21
  # are stored in the database, but only can be accessed directly by the
21
22
  # Sequel::Database object). It's a work in progress, be warned to use
22
23
  # carefully and motivated to contribute.
@@ -40,12 +41,10 @@ require 'rack'
40
41
  # To use this class with your (rack isn't?) app you need to: create the
41
42
  # necessary tables in a Sequel::Database object with Ballonizer.create_tables;
42
43
  # create a ballonizer instance with the url where you gonna handle the ballon
43
- # change requests. Handle the ballon changes request in that url with
44
- # process_submit. Call instance.ballonize_page over the html documents who can
45
- # have the images to be ballonized. Include the jquery, jquery-json, jquery-ui,
46
- # and ballonizer javascript libraries in the page (provided by the gem); Include
47
- # the ui-lightness and ballonizer css in the page (provided by the gem).
48
- # Check if the image match the css selector :img_to_ballonize_css_selector.
44
+ # change requests and where provide the assets. Handle the ballon changes request
45
+ # in that url with process_submit. Call instance.ballonize_page over the html
46
+ # documents that can have the images to be ballonized. Check if the image match
47
+ # the css selector :img_to_ballonize_css_selector.
49
48
  #
50
49
  # What's explained above is basically the example you can access with
51
50
  # 'rake example' and is in the examples/ballonizer_app/config.ru file.
@@ -64,23 +63,93 @@ class Ballonizer
64
63
 
65
64
  attr_accessor :db, :settings
66
65
 
66
+ # @api private Don't use the methods of this module. They are for internal use only.
67
+ module Workaround
68
+ def self.join_uris(base, relative)
69
+ base = base.end_with?('/') ? base : base + '/'
70
+ Addressable::URI.parse(base).join(relative).to_s
71
+ end
72
+
73
+ def self.deep_freeze(e)
74
+ e.each { | v | deep_freeze(v) } if e.is_a?(Enumerable)
75
+ e.freeze
76
+ end
77
+
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
80
+ #
81
+ # Example: <meta name="description" content="not important" />
82
+ # 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
84
+ 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
88
+ options = Nokogiri::XML::ParseOptions::DEFAULT_XML &
89
+ Nokogiri::XML::ParseOptions::STRICT &
90
+ Nokogiri::XML::ParseOptions::NONET
91
+ parsed_doc = Nokogiri::XML::Document.parse(doc, nil, nil, options)
92
+ rescue
93
+ parsed_doc = Nokogiri::HTML(doc)
94
+ end
95
+
96
+ parsed_doc
97
+ end
98
+ end
99
+
100
+ private_constant :Workaround
101
+
102
+ # The load paths of assets inside the gem and the files inside each path,
103
+ # in the order they need to be included (the files of the first path need
104
+ # to be included before the files in the second path, and the files in the
105
+ # same path need to be included in the specified order).
106
+ # Give preference to the asset(s)_* and *_html_links methods over this constant.
107
+ ASSETS = Workaround.deep_freeze([
108
+ ['vendor/assets/javascripts', [
109
+ 'jquery-2.0.1.js',
110
+ 'jquery.json-2.4.min.js',
111
+ 'jquery-ui-1.10.3.custom.min.js']],
112
+ ['lib/assets/javascripts', [
113
+ 'ballonizer.js']],
114
+ ['vendor/assets/stylesheets', [
115
+ 'ui-lightness/jquery-ui-1.10.3.custom.min.css']],
116
+ ['lib/assets/stylesheets', [
117
+ 'ballonizer.css']]
118
+ ])
119
+
67
120
  # The default #settings
68
121
  DEFAULT_SETTINGS = {
69
122
  # The css selector used to define the elements to ballonize.
70
123
  img_to_ballonize_css_selector: 'img.to_ballonize',
71
124
  # A url to be used in the client-side action attribute of the form for
72
- # ballon submition. The value will be used in the javascript snippet who
125
+ # ballon submition. The value will be used in the javascript snippet that
73
126
  # initialize the ballonizer client javascript allowing ballon edition
74
127
  # (and consequently creating the form).
75
128
  form_handler_url: '#',
76
- # Define if the javascript code who allow edition will be added to the page.
129
+ # Define if the javascript code that allow edition will be added to the page.
77
130
  # (this don't refer to the jquery-* libs and the ballonizer.js only the
78
131
  # snippet to execute when the page is ready)
79
- add_js_for_edition: true
132
+ add_js_for_edition: true,
133
+ # A path string to prefix each href of the css stylesheet links generated
134
+ # by the js_libs_html_links, and, possibly, added by the ballonize_page
135
+ # object. Example: if you use Ballonizer.assets_app mapped to '/assets'
136
+ # then use '/assets' here. This is used with the :add_required_css setting.
137
+ css_asset_path_for_link: nil,
138
+ # If the ballonize_page method will add or not the html generated by
139
+ # #css_html_links (require the :css_asset_path_for_link to be defined).
140
+ add_required_css: false,
141
+ # A path string to prefix each js source src generated by the
142
+ # object. Example: if you use Ballonizer.assets_app mapped to '/assets'
143
+ # then use '/assets' here. This is used with the
144
+ # :add_required_js_libs_for_edition setting.
145
+ js_asset_path_for_link: nil,
146
+ # If the ballonize_page method will add or not the html generated by
147
+ # #js_libs_html_links (require the :js_asset_path_for_link to be defined).
148
+ add_required_js_libs_for_edition: false
80
149
  }.freeze.each { | _, v| v.freeze }
81
150
 
82
151
  # Create a new Ballonizer object from a Sequel Database (with the expected
83
- # tables, who can be created with Ballonizer.create_tables) and a optional
152
+ # tables, that can be created with Ballonizer.create_tables) and a optional
84
153
  # hash of settings.
85
154
  # @param db [Sequel::Database] A Sequel::Database with tables as described
86
155
  # @param settings [Hash{Symbol => String}] A optional hash of settings. The
@@ -256,7 +325,9 @@ class Ballonizer
256
325
  end
257
326
 
258
327
  # Wrap each image to ballonize with a container, add its ballons to the
259
- # container and add the js snippet for the edition initialization.
328
+ # container and, possibly, add the css and js libs and snippet for the
329
+ # edition initialization. Don't make any change if the page has no images
330
+ # to ballonize.
260
331
  # @param page [String] The (X)HTML page.
261
332
  # @param page_url [String] The url of the page to be ballonized, necessary
262
333
  # to make absolute the src attribute of img (if it's relative).
@@ -284,9 +355,15 @@ class Ballonizer
284
355
  end
285
356
  end
286
357
 
358
+ head = parsed_page.at_css('head')
359
+ if settings[:add_required_css]
360
+ head.children.last.add_next_sibling(self.css_html_links)
361
+ end
362
+ if settings[:add_required_js_libs_for_edition]
363
+ head.children.last.add_next_sibling(self.js_libs_html_links)
364
+ end
287
365
  if settings[:add_js_for_edition]
288
- parsed_page.at_css('head').children.last
289
- .add_next_sibling(self.js_load_snippet)
366
+ head.children.last.add_next_sibling(self.js_load_snippet)
290
367
  end
291
368
  end
292
369
 
@@ -310,7 +387,7 @@ class Ballonizer
310
387
  # Don't use this method. It is for internal use only.
311
388
  # @note This method don't make distinction between a image in the database
312
389
  # without any ballons (removed in the last version, by example) or a image
313
- # who isn't in the database (both return a empty array).
390
+ # that isn't in the database (both return a empty array).
314
391
  def last_ballon_set_of_image(img_src)
315
392
  db_image = self.db[:images].first({img_src: img_src})
316
393
  if db_image
@@ -372,30 +449,118 @@ class Ballonizer
372
449
  end
373
450
  end
374
451
 
375
- # @api private Don't use the methods of this module. They are for internal use only.
376
- module Workaround
377
- def self.parse_html_or_xhtml(doc)
378
- # If you parse XHTML as HTML with Nokogiri and use to_s after the markup can be messed up
379
- #
380
- # Example: <meta name="description" content="not important" />
381
- # becomes <meta name="description" content="not important" >
382
- # To avoid this we parse a document who is XML valid as XML, and, otherwise as HTML
383
- parsed_doc = nil
384
- begin
385
- # this also isn't a great way to do this
386
- # the Nokogiri don't have exception classes, this way any StandardError will be silenced
387
- options = Nokogiri::XML::ParseOptions::DEFAULT_XML &
388
- Nokogiri::XML::ParseOptions::STRICT &
389
- Nokogiri::XML::ParseOptions::NONET
390
- parsed_doc = Nokogiri::XML::Document.parse(doc, nil, nil, options)
391
- rescue
392
- parsed_doc = Nokogiri::HTML(doc)
393
- end
452
+ # The (X)HTML fragment with the link tags that are added to the page by
453
+ # ballonize_page if the :add_required_css setting is true (the default
454
+ # is false). If the :css_asset_path_for_link isn't set (what is the
455
+ # default) this method return nil.
456
+ # @return [String,NilClass] A frozen string.
457
+ def css_html_links
458
+ return nil unless settings[:css_asset_path_for_link]
459
+ return @css_html_links if @css_html_links
460
+
461
+ link_template = '<link rel="stylesheet" type="text/css" href="PATH" />'
462
+ css_paths = self.class.asset_logical_paths.select do | p |
463
+ /^.+\.css$/.match(p)
464
+ end
394
465
 
395
- parsed_doc
466
+ links = css_paths.map do | p |
467
+ p = Workaround.join_uris(settings[:css_asset_path_for_link], p)
468
+ link_template.sub('PATH', p)
396
469
  end
470
+
471
+ @css_html_links = links.join('').freeze
397
472
  end
398
473
 
399
- private_constant :Workaround
474
+ # The (X)HTML fragment with the script tags that are added to the page by
475
+ # ballonize_page if the :add_required_js_libs_for_edition setting is true
476
+ # (the default is false). If the :js_asset_path_for_link isn't set (what
477
+ # is the default) this method return nil.
478
+ # @return [String,NilClass] A frozen string.
479
+ def js_libs_html_links
480
+ return nil unless settings[:js_asset_path_for_link]
481
+ return @js_libs_html_links if @js_libs_html_links
482
+
483
+ link_template = '<script type="text/javascript" src="PATH" />'
484
+ js_libs_paths = self.class.asset_logical_paths.select do | p |
485
+ /^.+\.js$/.match(p)
486
+ end
487
+
488
+ links = js_libs_paths.map do | p |
489
+ p = Workaround.join_uris(settings[:js_asset_path_for_link], p)
490
+ link_template.sub('PATH', p)
491
+ end
492
+
493
+ @js_libs_html_links = links.join('').freeze
494
+ end
495
+
496
+ # List of paths (relative to the gem root directory) to the directories with
497
+ # the css and js provided by the gem.
498
+ # @return [Array<String>] A frozen array of frozen strings.
499
+ def self.asset_load_paths
500
+ return @asset_load_paths if @asset_load_paths
501
+
502
+ absolute_lib_dir = File.dirname(File.realpath(__FILE__))
503
+ ballonizer_gem_root_dir = File.expand_path('../', absolute_lib_dir)
504
+
505
+ @asset_load_paths = ASSETS.map do | load_path_and_files |
506
+ load_path = load_path_and_files.first
507
+ File.expand_path(load_path, ballonizer_gem_root_dir)
508
+ end
509
+
510
+ @asset_load_paths.flatten!
511
+ @asset_load_paths.freeze
512
+ end
513
+
514
+ # List of logical paths to the css and js assets. The assets_app respond to
515
+ # any requisition to one of these paths.
516
+ # @return [Array<String>] A frozen array of frozen strings.
517
+ def self.asset_logical_paths
518
+ return @asset_logical_paths if @asset_logical_paths
519
+
520
+ @asset_logical_paths = ASSETS.map do | load_path_and_files |
521
+ load_path_and_files.last
522
+ end
523
+
524
+ @asset_logical_paths.flatten!
525
+ @asset_logical_paths.freeze
526
+ end
527
+
528
+ # List of absolute filepaths to the css and js files needed by the client
529
+ # counterpart and provided by the gem. To all who not want to use assets_app.
530
+ # @return [Array<String>] A frozen array of frozen strings.
531
+ # @see Ballonizer.assets_app
532
+ def self.asset_absolute_paths
533
+ return @asset_absolute_paths if @asset_absolute_paths
534
+
535
+ absolute_lib_dir = File.dirname(File.realpath(__FILE__))
536
+ ballonizer_gem_root_dir = File.expand_path('../', absolute_lib_dir)
537
+
538
+ @asset_absolute_paths = ASSETS.map do | load_path_and_files |
539
+ relative_load_path, filepaths = *load_path_and_files
540
+ absolute_load_path = File.expand_path(relative_load_path, ballonizer_gem_root_dir)
541
+
542
+ filepaths.map do | filepath |
543
+ File.expand_path(filepath, absolute_load_path)
544
+ end
545
+ end
546
+
547
+ @asset_absolute_paths.flatten!
548
+ @asset_absolute_paths.freeze
549
+ end
550
+
551
+ # A Rack app that provide the gem css and js. Each call to this method return
552
+ # a new object (clone). The Sprockets::Environment isn't frozen because it
553
+ # can't be used with 'run' in a rack app if frozen.
554
+ # @return [Sprockets::Environment]
555
+ # @see Ballonizer.assets_app
556
+ def self.assets_app
557
+ # dont freeze because run don't work in a frozen sprockets env
558
+ return @assets_app.clone if @assets_app
559
+ @assets_app = Sprockets::Environment.new
560
+ asset_load_paths.each do | load_path |
561
+ @assets_app.prepend_path load_path
562
+ end
563
+ @assets_app.clone
564
+ end
400
565
  end
401
566
 
@@ -3,6 +3,7 @@ require 'rspec-html-matchers'
3
3
  # Avoid to use equivalent-xml, the specs break with cosmetic changes this way
4
4
  require 'equivalent-xml'
5
5
  require 'stringio'
6
+ require 'rexml/document'
6
7
 
7
8
  # make the changes in the BD restricted to the example
8
9
  RSpec.configure do |c|
@@ -11,6 +12,26 @@ RSpec.configure do |c|
11
12
  end
12
13
  end
13
14
 
15
+ RSpec::Matchers.define :exist_in_filesystem do
16
+ match do | actual |
17
+ if actual.respond_to? :all?
18
+ actual.all? { | filename | File.exist?(filename) }
19
+ else
20
+ File.exist? actual
21
+ end
22
+ end
23
+ failure_message_for_should do | actual |
24
+ if actual.respond_to? :all?
25
+ "expected #{actual} to be a list of absolute filepaths for existing files or directories"
26
+ else
27
+ "expected #{actual} to be a absolute filepath for an existing file or directory"
28
+ end
29
+ end
30
+ description do
31
+ "be a list of absolute paths for existing files or directories"
32
+ end
33
+ end
34
+
14
35
  describe Ballonizer do
15
36
 
16
37
  DB = Sequel.sqlite
@@ -63,8 +84,11 @@ describe Ballonizer do
63
84
  # Definitions ending with '_example' are to be cloned and defined in a
64
85
  # context without the sufix. Definitions without the sufix are used in the
65
86
  # specs and may require the definition of some without '_example' counterparts.
87
+ let (:ballonizer_settings_example) do
88
+ {}
89
+ end
66
90
  let (:ballonizer_new_args_example) do
67
- [DB, {}]
91
+ [DB, ballonizer_settings]
68
92
  end
69
93
  # TODO: This isn't a valid Rack env, to turn it in a valid env will be
70
94
  # necessary to add all obrigatory 'rack.' variables described in:
@@ -123,6 +147,7 @@ describe Ballonizer do
123
147
 
124
148
  # definitions to be overriden in specific contexts (by doing a clone of the
125
149
  # '_example' counterpart and changing what is needed)
150
+ let (:ballonizer_settings) { ballonizer_settings_example }
126
151
  let (:ballonizer_new_args) { ballonizer_new_args_example }
127
152
  let (:env) { env_example }
128
153
  let (:original_page) { original_page_example.to_s }
@@ -173,6 +198,35 @@ describe Ballonizer do
173
198
  end)
174
199
  end
175
200
  end
201
+ # TODO: create this specs
202
+ context 'and the settings define to insert css' do
203
+ let (:ballonizer_settings) do
204
+ ballonizer_settings_example.merge({
205
+ add_required_css: true,
206
+ css_asset_path_for_link: 'assets/css'
207
+ })
208
+ end
209
+
210
+ it do
211
+ # the example html don't have any link tag, so if there one
212
+ # it was added by the method
213
+ should have_tag('link', :with => { type: 'text/css' })
214
+ end
215
+ end
216
+ context 'and the settings define to insert js' do
217
+ let (:ballonizer_settings) do
218
+ ballonizer_settings_example.merge({
219
+ # this option is added to avoid a false positive in the test
220
+ add_js_for_edition: false,
221
+ add_required_js_libs_for_edition: true,
222
+ js_asset_path_for_link: 'assets/js'
223
+ })
224
+ end
225
+
226
+ it do
227
+ should have_tag('script', :with => { type: 'text/javascript' })
228
+ end
229
+ end
176
230
  end
177
231
  context 'and more than one of them is to be ballonized' do
178
232
  let (:original_page) do
@@ -488,7 +542,6 @@ describe Ballonizer do
488
542
  end
489
543
 
490
544
  describe '#process_submit' do
491
-
492
545
  # As process_submit use the #valid_submit_json? and #process_submit_hash
493
546
  # we only make the tests here for example and its not the ideia cover the
494
547
  # cases already covered in the specs of the two methods
@@ -535,5 +588,103 @@ describe Ballonizer do
535
588
  end
536
589
  end
537
590
  end
591
+
592
+ describe '#css_html_links' do
593
+ context "when the path for the css isn't configured" do
594
+ it 'returns nil' do
595
+ expect(instance.css_html_links).to eq nil
596
+ end
597
+ end
598
+ context 'when the path for the css is configured' do
599
+ let (:ballonizer_settings) do
600
+ ballonizer_settings_example.merge({
601
+ css_asset_path_for_link: 'assets/css'
602
+ })
603
+ end
604
+
605
+ it 'return a HTML string with links prefixed by the path' do
606
+ REXML::Document.new("<root>#{instance.css_html_links}</root>")
607
+ .root.children.each do | e |
608
+ expect(e.attributes['href']).to match(/^assets\/css/)
609
+ end
610
+ end
611
+ end
612
+ end
613
+
614
+ describe '#js_libs_html_links' do
615
+ context "when the path for the js libs isn't configured" do
616
+ it 'returns nil' do
617
+ expect(instance.js_libs_html_links).to eq nil
618
+ end
619
+ end
620
+ context 'when the path for the js libs is configured' do
621
+ let (:ballonizer_settings) do
622
+ ballonizer_settings_example.merge({
623
+ js_asset_path_for_link: 'assets/js'
624
+ })
625
+ end
626
+
627
+ it 'return a HTML string with links prefixed by the path' do
628
+ REXML::Document.new("<root>#{instance.js_libs_html_links}</root>")
629
+ .root.children.each do | e |
630
+ expect(e.attributes['src']).to match(/^assets\/js/)
631
+ end
632
+ end
633
+ end
634
+ end
635
+
636
+ describe '.asset_load_paths' do
637
+ it 'return an array of paths who exist in the gem root dir' do
638
+ # TODO: change to __dir__ when the 2.0 become widely adopted
639
+ spec_dir = File.dirname(File.realpath(__FILE__))
640
+ gem_root_dir = File.expand_path('../', spec_dir)
641
+
642
+ absolute_load_paths = described_class.asset_load_paths.map do | path |
643
+ File.expand_path(path, gem_root_dir)
644
+ end
645
+
646
+ expect(absolute_load_paths).to exist_in_filesystem
647
+ end
648
+ end
649
+
650
+ describe '.asset_absolute_paths' do
651
+ it 'return an array of absolute filepaths who exist' do
652
+ expect(described_class.asset_absolute_paths).to exist_in_filesystem
653
+ end
654
+ end
655
+
656
+ describe '.asset_logical_paths' do
657
+ it 'return the last part of the asset_absolute_paths' do
658
+ logical_paths = described_class.asset_logical_paths
659
+ absolute_paths = described_class.asset_absolute_paths
660
+
661
+ are_last_part_of_absolute = logical_paths.all? do | lp |
662
+ absolute_paths.any? { | ap | ap.end_with? lp }
663
+ end
664
+
665
+ expect(are_last_part_of_absolute).to be_true
666
+ end
667
+ end
668
+
669
+ describe '.assets_app' do
670
+ let (:envs) do
671
+ described_class.asset_logical_paths.map do | asset_logical_path |
672
+ env_example.merge({
673
+ 'HTTP_HOST' => 'example.net',
674
+ 'SERVER_NAME' => 'example.net',
675
+ 'PATH_INFO' => asset_logical_path,
676
+ 'REQUEST_METHOD' => 'GET',
677
+ 'rack.input' => nil,
678
+ 'CONTENT_TYPE' => nil,
679
+ 'CONTENT_LENGTH' => nil
680
+ })
681
+ end
682
+ end
683
+ it 'provide the css and js libs required by the gem' do
684
+ envs.each do | env |
685
+ expect(described_class.assets_app.call(env)[0]).to eq 200
686
+ end
687
+ end
688
+ end
538
689
  end
539
690