unpoly-rails 0.57.0 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +393 -1
  3. data/Gemfile.lock +5 -2
  4. data/README.md +1 -1
  5. data/README_RAILS.md +1 -1
  6. data/Rakefile +10 -1
  7. data/design/es6.js +32 -0
  8. data/design/ie11.txt +9 -0
  9. data/design/measure_jquery/element_list.js +41 -0
  10. data/design/measure_jquery/up.on_vs_addEventListener.js +56 -0
  11. data/design/todo_jquery.txt +13 -0
  12. data/dist/unpoly-bootstrap3.js +8 -8
  13. data/dist/unpoly-bootstrap3.min.js +1 -1
  14. data/dist/unpoly.css +22 -20
  15. data/dist/unpoly.js +6990 -5336
  16. data/dist/unpoly.min.css +1 -1
  17. data/dist/unpoly.min.js +4 -4
  18. data/lib/assets/javascripts/unpoly-bootstrap3/viewport-ext.coffee +5 -0
  19. data/lib/assets/javascripts/unpoly.coffee +8 -6
  20. data/lib/assets/javascripts/unpoly/browser.coffee.erb +23 -118
  21. data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +36 -0
  22. data/lib/assets/javascripts/unpoly/classes/cache.coffee +4 -4
  23. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +45 -39
  24. data/lib/assets/javascripts/unpoly/classes/config.coffee +9 -0
  25. data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +18 -27
  26. data/lib/assets/javascripts/unpoly/classes/divertible_chain.coffee +39 -0
  27. data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +116 -0
  28. data/lib/assets/javascripts/unpoly/classes/extract_cascade.coffee +8 -8
  29. data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +19 -19
  30. data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +54 -31
  31. data/lib/assets/javascripts/unpoly/classes/{focus_tracker.coffee → focus_follower.coffee} +2 -2
  32. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +25 -25
  33. data/lib/assets/javascripts/unpoly/classes/html_parser.coffee +4 -11
  34. data/lib/assets/javascripts/unpoly/classes/motion_controller.coffee +157 -0
  35. data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +525 -0
  36. data/lib/assets/javascripts/unpoly/classes/record.coffee +8 -2
  37. data/lib/assets/javascripts/unpoly/classes/rect.js +21 -0
  38. data/lib/assets/javascripts/unpoly/classes/request.coffee +41 -35
  39. data/lib/assets/javascripts/unpoly/classes/response.coffee +7 -3
  40. data/lib/assets/javascripts/unpoly/classes/reveal_motion.coffee +102 -0
  41. data/lib/assets/javascripts/unpoly/classes/scroll_motion.coffee +67 -0
  42. data/lib/assets/javascripts/unpoly/classes/selector.coffee +60 -0
  43. data/lib/assets/javascripts/unpoly/classes/tether.coffee +105 -0
  44. data/lib/assets/javascripts/unpoly/classes/url_set.coffee +12 -7
  45. data/lib/assets/javascripts/unpoly/element.coffee.erb +1126 -0
  46. data/lib/assets/javascripts/unpoly/event.coffee.erb +437 -0
  47. data/lib/assets/javascripts/unpoly/feedback.coffee +73 -94
  48. data/lib/assets/javascripts/unpoly/form.coffee.erb +188 -181
  49. data/lib/assets/javascripts/unpoly/{dom.coffee.erb → fragment.coffee.erb} +250 -283
  50. data/lib/assets/javascripts/unpoly/framework.coffee +67 -0
  51. data/lib/assets/javascripts/unpoly/history.coffee +29 -28
  52. data/lib/assets/javascripts/unpoly/legacy.coffee +60 -0
  53. data/lib/assets/javascripts/unpoly/link.coffee.erb +127 -119
  54. data/lib/assets/javascripts/unpoly/log.coffee +99 -19
  55. data/lib/assets/javascripts/unpoly/modal.coffee.erb +95 -118
  56. data/lib/assets/javascripts/unpoly/motion.coffee.erb +158 -138
  57. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +0 -5
  58. data/lib/assets/javascripts/unpoly/popup.coffee.erb +119 -102
  59. data/lib/assets/javascripts/unpoly/protocol.coffee +11 -15
  60. data/lib/assets/javascripts/unpoly/proxy.coffee +62 -65
  61. data/lib/assets/javascripts/unpoly/radio.coffee +3 -5
  62. data/lib/assets/javascripts/unpoly/rails.coffee +8 -9
  63. data/lib/assets/javascripts/unpoly/syntax.coffee.erb +173 -125
  64. data/lib/assets/javascripts/unpoly/toast.coffee +25 -24
  65. data/lib/assets/javascripts/unpoly/tooltip.coffee +89 -79
  66. data/lib/assets/javascripts/unpoly/util.coffee.erb +579 -1074
  67. data/lib/assets/javascripts/unpoly/{layout.coffee.erb → viewport.coffee.erb} +334 -264
  68. data/lib/assets/stylesheets/unpoly/dom.sass +1 -1
  69. data/lib/assets/stylesheets/unpoly/layout.sass +2 -0
  70. data/lib/assets/stylesheets/unpoly/popup.sass +0 -1
  71. data/lib/assets/stylesheets/unpoly/tooltip.sass +17 -12
  72. data/lib/unpoly/rails/version.rb +1 -1
  73. data/package.json +1 -2
  74. data/spec_app/Gemfile +2 -1
  75. data/spec_app/Gemfile.lock +38 -27
  76. data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
  77. data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -2
  78. data/spec_app/app/assets/stylesheets/integration_test.sass +14 -1
  79. data/spec_app/app/controllers/scroll_test_controller.rb +5 -0
  80. data/spec_app/app/views/css_test/modal.erb +6 -6
  81. data/spec_app/app/views/css_test/popup.erb +44 -18
  82. data/spec_app/app/views/css_test/tooltip.erb +23 -4
  83. data/spec_app/app/views/error_test/trigger.erb +1 -1
  84. data/spec_app/app/views/form_test/basics/new.erb +1 -3
  85. data/spec_app/app/views/pages/start.erb +9 -2
  86. data/spec_app/app/views/reveal_test/long1.erb +1 -1
  87. data/spec_app/app/views/reveal_test/long2.erb +1 -1
  88. data/spec_app/app/views/reveal_test/within_document_viewport.erb +24 -0
  89. data/spec_app/app/views/reveal_test/within_overflowing_div_viewport.erb +28 -0
  90. data/spec_app/app/views/scroll_test/long1.erb +30 -0
  91. data/spec_app/config/routes.rb +1 -0
  92. data/spec_app/spec/javascripts/helpers/agent_detector.coffee +3 -0
  93. data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +1 -0
  94. data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +17 -5
  95. data/spec_app/spec/javascripts/helpers/enable_logging.js.coffee +1 -1
  96. data/spec_app/spec/javascripts/helpers/fixture.js.coffee +25 -0
  97. data/spec_app/spec/javascripts/helpers/jquery_no_conflict.js +1 -0
  98. data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -0
  99. data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +1 -1
  100. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +2 -2
  101. data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +4 -1
  102. data/spec_app/spec/javascripts/helpers/remove_body_margin.js.coffee +3 -0
  103. data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +2 -1
  104. data/spec_app/spec/javascripts/helpers/reset_knife.js.coffee +2 -2
  105. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +18 -11
  106. data/spec_app/spec/javascripts/helpers/restore_body_scroll.js.coffee +3 -0
  107. data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +3 -0
  108. data/spec_app/spec/javascripts/helpers/spec_util.coffee +47 -0
  109. data/spec_app/spec/javascripts/helpers/to_be_around.js.coffee +3 -0
  110. data/spec_app/spec/javascripts/helpers/to_be_array.coffee +5 -0
  111. data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +6 -2
  112. data/spec_app/spec/javascripts/helpers/to_be_blank.js.coffee +3 -0
  113. data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +6 -2
  114. data/spec_app/spec/javascripts/helpers/to_be_element.js.coffee +8 -0
  115. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +3 -0
  116. data/spec_app/spec/javascripts/helpers/to_be_given.js.coffee +3 -0
  117. data/spec_app/spec/javascripts/helpers/to_be_hidden.js.coffee +8 -0
  118. data/spec_app/spec/javascripts/helpers/to_be_missing.js.coffee +3 -0
  119. data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +3 -0
  120. data/spec_app/spec/javascripts/helpers/to_be_scrolled_to.coffee +3 -0
  121. data/spec_app/spec/javascripts/helpers/to_be_visible.js.coffee +9 -0
  122. data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +3 -0
  123. data/spec_app/spec/javascripts/helpers/to_end_with.js.coffee +3 -0
  124. data/spec_app/spec/javascripts/helpers/to_equal_jquery.js.coffee +1 -2
  125. data/spec_app/spec/javascripts/helpers/to_equal_node_list.coffee +7 -0
  126. data/spec_app/spec/javascripts/helpers/to_equal_via_is_equal.js.coffee +7 -0
  127. data/spec_app/spec/javascripts/helpers/to_have_class.js.coffee +10 -0
  128. data/spec_app/spec/javascripts/helpers/to_have_descendant.js.coffee +10 -0
  129. data/spec_app/spec/javascripts/helpers/to_have_length.js.coffee +8 -0
  130. data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +7 -3
  131. data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +3 -0
  132. data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +1 -0
  133. data/spec_app/spec/javascripts/helpers/to_have_text.js.coffee +9 -0
  134. data/spec_app/spec/javascripts/helpers/to_have_unhandled_rejections.coffee +0 -21
  135. data/spec_app/spec/javascripts/helpers/to_match_list.coffee +14 -0
  136. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +3 -0
  137. data/spec_app/spec/javascripts/helpers/to_match_text.js.coffee +4 -1
  138. data/spec_app/spec/javascripts/helpers/to_match_url.coffee +1 -0
  139. data/spec_app/spec/javascripts/helpers/trigger.js.coffee +91 -7
  140. data/spec_app/spec/javascripts/helpers/wait_until_dom_ready.js.coffee +3 -0
  141. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +23 -90
  142. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +3 -0
  143. data/spec_app/spec/javascripts/up/classes/config_spec.coffee +24 -0
  144. data/spec_app/spec/javascripts/up/classes/divertible_chain_spec.coffee +45 -0
  145. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +5 -2
  146. data/spec_app/spec/javascripts/up/classes/params_spec.coffee +557 -0
  147. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +7 -4
  148. data/spec_app/spec/javascripts/up/classes/scroll_motion_spec.js.coffee +51 -0
  149. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +3 -0
  150. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +3 -2
  151. data/spec_app/spec/javascripts/up/element_spec.coffee +897 -0
  152. data/spec_app/spec/javascripts/up/event_spec.js.coffee +496 -0
  153. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +69 -48
  154. data/spec_app/spec/javascripts/up/form_spec.js.coffee +252 -194
  155. data/spec_app/spec/javascripts/up/{dom_spec.js.coffee → fragment_spec.js.coffee} +381 -388
  156. data/spec_app/spec/javascripts/up/history_spec.js.coffee +21 -19
  157. data/spec_app/spec/javascripts/up/jquery_spec.js.coffee +4 -0
  158. data/spec_app/spec/javascripts/up/legacy_spec.js.coffee +27 -0
  159. data/spec_app/spec/javascripts/up/link_spec.js.coffee +163 -160
  160. data/spec_app/spec/javascripts/up/log_spec.js.coffee +85 -12
  161. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +141 -123
  162. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +117 -113
  163. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +60 -77
  164. data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +1 -0
  165. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +85 -78
  166. data/spec_app/spec/javascripts/up/radio_spec.js.coffee +29 -22
  167. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +14 -13
  168. data/spec_app/spec/javascripts/up/spec_spec.js.coffee +9 -0
  169. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +96 -66
  170. data/spec_app/spec/javascripts/up/toast_spec.js.coffee +37 -0
  171. data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +31 -47
  172. data/spec_app/spec/javascripts/up/util_spec.js.coffee +725 -562
  173. data/spec_app/spec/javascripts/up/{layout_spec.js.coffee → viewport_spec.js.coffee} +175 -149
  174. metadata +57 -19
  175. data/lib/assets/javascripts/unpoly-bootstrap3/layout-ext.coffee +0 -5
  176. data/lib/assets/javascripts/unpoly/bus.coffee.erb +0 -518
  177. data/lib/assets/javascripts/unpoly/classes/extract_step.coffee +0 -4
  178. data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +0 -125
  179. data/lib/assets/javascripts/unpoly/params.coffee.erb +0 -522
  180. data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +0 -8
  181. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +0 -210
  182. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +0 -9
  183. data/spec_app/spec/javascripts/up/params_spec.coffee +0 -768
  184. data/spec_app/vendor/asset-libs/jasmine-fixture-1.3.4/jasmine-fixture.js +0 -433
  185. data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/.bower.json +0 -26
  186. data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/jasmine-jquery.js +0 -838
@@ -1,3 +1,6 @@
1
+ u = up.util
2
+ $ = jQuery
3
+
1
4
  describe 'up.Request', ->
2
5
 
3
6
  describe '#url', ->
@@ -39,12 +42,12 @@ describe 'up.Request', ->
39
42
  it 'returns the constructed params for HTTP methods that allow a payload', ->
40
43
  params = { key: 'value' }
41
44
  request = new up.Request(url: 'http://host.com/foo', params: params, method: 'post')
42
- expect(request.params).toEqual(params)
45
+ expect(request.params).toEqual(new up.Params(params))
43
46
 
44
- it "returns undefined for HTTP methods that don't allow a payload", ->
47
+ it "returns a blank up.Params object for HTTP methods that don't allow a payload", ->
45
48
  request = new up.Request(url: 'http://host.com/foo', params: { key: 'value' }, method: 'get')
46
- expect(request.params).toBeUndefined()
49
+ expect(request.params).toBeBlank()
47
50
 
48
51
  it 'returns the merged { params } and params from the URL for HTTP methods that allow a payload', ->
49
52
  request = new up.Request(url: 'http://host.com/foo?urlKey=urlValue', params: { paramsKey: 'paramsValue' }, method: 'post')
50
- expect(request.params).toEqual(paramsKey: 'paramsValue', urlKey: 'urlValue')
53
+ expect(request.params).toEqual(new up.Params(paramsKey: 'paramsValue', urlKey: 'urlValue'))
@@ -0,0 +1,51 @@
1
+ u = up.util
2
+ $ = jQuery
3
+
4
+ describe 'up.ScrollMotion', ->
5
+
6
+ beforeEach ->
7
+ @$viewport = $fixture('.viewport').css
8
+ height: '100px'
9
+ overflowY: 'scroll'
10
+
11
+ @$content = @$viewport.affix('.content').css
12
+ height: '10000px'
13
+
14
+ describe 'constructor', ->
15
+
16
+ it 'does not start scrolling', ->
17
+ motion = new up.ScrollMotion(@$viewport[0], 530)
18
+ expect(@$viewport.scrollTop()).toEqual(0)
19
+
20
+ describe '#start()', ->
21
+
22
+ describe '(without { behavior } option)', ->
23
+
24
+ it 'abruptly scrolls the given element to the given y-position', ->
25
+ motion = new up.ScrollMotion(@$viewport[0], 530)
26
+
27
+ motion.start()
28
+
29
+ expect(@$viewport.scrollTop()).toEqual(530)
30
+
31
+ describe '(with { behavior: "scroll" } option)', ->
32
+
33
+ it 'animates the scrolling to the given y-position', asyncSpec (next) ->
34
+ motion = new up.ScrollMotion(@$viewport[0], 2050, { behavior: 'smooth' })
35
+
36
+ scrollDone = motion.start()
37
+
38
+ next.after 100, =>
39
+ expect(@$viewport.scrollTop()).toBeAround(1, 500)
40
+
41
+ next.await(scrollDone)
42
+
43
+ next =>
44
+ expect(@$viewport.scrollTop())
45
+
46
+ it "aborts the scrolling animation if the user or another script changes the viewport's scrollTop during the animation"
47
+
48
+ describe '#finish()', ->
49
+
50
+ it 'abruptly finishes the scrolling animation by setting the target y-position'
51
+
@@ -1,3 +1,6 @@
1
+ u = up.util
2
+ $ = jQuery
3
+
1
4
  describe 'up.store.Memory', ->
2
5
 
3
6
  describe '#get', ->
@@ -1,6 +1,7 @@
1
- describe 'up.store.Session', ->
1
+ u = up.util
2
+ $ = jQuery
2
3
 
3
- u = up.util
4
+ describe 'up.store.Session', ->
4
5
 
5
6
  afterEach ->
6
7
  sessionStorage.removeItem('spec')
@@ -0,0 +1,897 @@
1
+ u = up.util
2
+ $ = jQuery
3
+
4
+ describe 'up.element', ->
5
+
6
+ describe 'up.element.get()', ->
7
+
8
+ it 'returns the given element', ->
9
+ element = fixture('.element')
10
+ expect(up.element.get(element)).toBe(element)
11
+
12
+ it 'returns the first element in the given jQuery collection', ->
13
+ $element = $fixture('.element')
14
+ expect(up.element.get($element)).toBe($element[0])
15
+
16
+ it 'throws an error if passed a jQuery collection with multiple elements', ->
17
+ $element1 = $fixture('.element')
18
+ $element2 = $fixture('.element')
19
+ $list = $element1.add($element2)
20
+ get = -> up.element.get($list)
21
+ expect(get).toThrowError(/cannot cast/i)
22
+
23
+ it 'returns the first element matching the given CSS selector string', ->
24
+ match = fixture('.match')
25
+ expect(up.element.get('.match')).toBe(match)
26
+
27
+ it 'returns the given document object', ->
28
+ expect(up.element.get(document)).toBe(document)
29
+
30
+ it 'returns the given window object', ->
31
+ expect(up.element.get(window)).toBe(window)
32
+
33
+ it 'returns undefined for undefined', ->
34
+ expect(up.element.get(undefined)).toBeUndefined()
35
+
36
+ it 'returns null for null', ->
37
+ expect(up.element.get(null)).toBeNull()
38
+
39
+ describe 'up.element.first()', ->
40
+
41
+ it 'returns the first element matching the given selector', ->
42
+ match = fixture('.match')
43
+ otherMatch = fixture('.match')
44
+ noMatch = fixture('.no-match')
45
+ result = up.element.first('.match')
46
+ expect(result).toBe(match)
47
+
48
+ it 'supports the custom :has() selector', ->
49
+ match = fixture('.match')
50
+ otherMatch = fixture('.match')
51
+ otherMatchChild = up.element.affix(otherMatch, '.child')
52
+
53
+ result = up.element.first('.match:has(.child)')
54
+ expect(result).toBe(otherMatch)
55
+
56
+ describe 'when given a root element for the search', ->
57
+
58
+ it 'returns the first descendant of the given root that matches the given selector', ->
59
+ $element = $fixture('.element')
60
+ $matchingChild = $element.affix('.child.match')
61
+ $matchingGrandChild = $matchingChild.affix('.grand-child.match')
62
+ $otherChild = $element.affix('.child')
63
+ $otherGrandChild = $otherChild.affix('.grand-child')
64
+ result = up.element.first($element[0], '.match')
65
+ expect(result).toEqual $matchingChild[0]
66
+
67
+ it 'returns missing if no descendant matches', ->
68
+ $element = $fixture('.element')
69
+ $child = $element.affix('.child')
70
+ $grandChild = $child.affix('.grand-child')
71
+ result = up.element.first($element[0], '.match')
72
+ expect(result).toBeMissing()
73
+
74
+ it 'does not return the root itself, even if it matches', ->
75
+ $element = $fixture('.element.match')
76
+ result = up.element.first($element[0], '.match')
77
+ expect(result).toBeMissing()
78
+
79
+ it 'does not return an ancestor of the root, even if it matches', ->
80
+ $parent = $fixture('.parent.match')
81
+ $element = $parent.affix('.element')
82
+ result = up.element.first($element[0], '.match')
83
+ expect(result).toBeMissing()
84
+
85
+ it 'supports the custom :has() selector', ->
86
+ $element = $fixture('.element')
87
+ $childWithSelectorWithChild = $element.affix('.selector')
88
+ $childWithSelectorWithChild.affix('.match')
89
+ $childWithSelectorWithoutChild = $element.affix('.selector')
90
+ $childWithoutSelectorWithChild = $element.affix('.other-selector')
91
+ $childWithoutSelectorWithChild.affix('.match')
92
+ $childWithoutSelectorWithoutChild = $fixture('.other-selector')
93
+
94
+ result = up.element.first($element[0], '.selector:has(.match)')
95
+ expect(result).toBe $childWithSelectorWithChild[0]
96
+
97
+ it 'supports the custom :has() selector when a previous sibling only matches its own selector, but not the descendant selector (bugfix)', ->
98
+ $element = $fixture('.element')
99
+ $childWithSelectorWithoutChild = $element.affix('.selector')
100
+ $childWithSelectorWithChild = $element.affix('.selector')
101
+ $childWithSelectorWithChild.affix('.match')
102
+
103
+ result = up.element.first($element[0], '.selector:has(.match)')
104
+ expect(result).toBe $childWithSelectorWithChild[0]
105
+
106
+ describe 'up.element.all()', ->
107
+
108
+ it 'returns all elements matching the given selector', ->
109
+ match = fixture('.match')
110
+ otherMatch = fixture('.match')
111
+ noMatch = fixture('.no-match')
112
+ result = up.element.all('.match')
113
+ expect(result).toMatchList [match, otherMatch]
114
+
115
+ it 'supports the custom :has() selector'
116
+
117
+ describe 'when given a root element for the search', ->
118
+
119
+ it 'returns all descendants of the given root matching the given selector', ->
120
+ $element = $fixture('.element')
121
+ $matchingChild = $element.affix('.child.match')
122
+ $matchingGrandChild = $matchingChild.affix('.grand-child.match')
123
+ $otherChild = $element.affix('.child')
124
+ $otherGrandChild = $otherChild.affix('.grand-child')
125
+ results = up.element.all($element[0], '.match')
126
+ expect(results).toEqual [$matchingChild[0], $matchingGrandChild[0]]
127
+
128
+ it 'returns an empty list if no descendant matches', ->
129
+ $element = $fixture('.element')
130
+ $child = $element.affix('.child')
131
+ $grandChild = $child.affix('.grand-child')
132
+ results = up.element.all($element[0], '.match')
133
+ expect(results).toEqual []
134
+
135
+ it 'does not return the root itself, even if it matches', ->
136
+ $element = $fixture('.element.match')
137
+ results = up.element.all($element[0], '.match')
138
+ expect(results).toEqual []
139
+
140
+ it 'does not return ancestors of the root, even if they match', ->
141
+ $parent = $fixture('.parent.match')
142
+ $element = $parent.affix('.element')
143
+ results = up.element.all($element[0], '.match')
144
+ expect(results).toEqual []
145
+
146
+ it 'supports the custom :has() selector', ->
147
+ $element = $fixture('.element')
148
+ $childWithSelectorWithChild = $element.affix('.selector')
149
+ $childWithSelectorWithChild.affix('.match')
150
+ $childWithSelectorWithoutChild = $element.affix('.selector')
151
+ $childWithoutSelectorWithChild = $element.affix('.other-selector')
152
+ $childWithoutSelectorWithChild.affix('.match')
153
+ $childWithoutSelectorWithoutChild = $fixture('.other-selector')
154
+
155
+ results = up.element.all($element[0], '.selector:has(.match)')
156
+ expect(results).toEqual [$childWithSelectorWithChild[0]]
157
+
158
+ describe 'up.element.list()', ->
159
+
160
+ it 'returns the given array of elements', ->
161
+ array = [document.body]
162
+ result = up.element.list(array)
163
+ expect(result).toEqual(array)
164
+
165
+ it 'returns an empty list for undefined', ->
166
+ result = up.element.list(undefined)
167
+ expect(result).toEqual []
168
+
169
+ it 'returns an empty list for null', ->
170
+ result = up.element.list(null)
171
+ expect(result).toEqual []
172
+
173
+ it 'converts a NodeList to an array', ->
174
+ nodeList = document.querySelectorAll('body')
175
+ result = up.element.list(nodeList)
176
+ expect(u.isArray(result)).toBe(true)
177
+ expect(result.length).toBe(1)
178
+ expect(result[0]).toBe(document.body)
179
+
180
+ it 'returns a concatenated array from multiple lists and elements', ->
181
+ div0 = $fixture('div.div0')[0]
182
+ div1 = $fixture('div.div1')[0]
183
+ div2 = $fixture('div.div2')[0]
184
+ div3 = $fixture('div.div3')[0]
185
+
186
+ result = up.element.list(div0, [div1, div2], div3)
187
+ expect(u.isArray(result)).toBe(true)
188
+ expect(result).toEqual [div0, div1, div2, div3]
189
+
190
+ it 'ignores missing values when concatenating arrays', ->
191
+ div0 = $fixture('div.div0')[0]
192
+ div1 = $fixture('div.div1')[0]
193
+ div2 = $fixture('div.div2')[0]
194
+ div3 = $fixture('div.div3')[0]
195
+
196
+ result = up.element.list(null, div0, [div1, div2], undefined, div3)
197
+ expect(u.isArray(result)).toBe(true)
198
+ expect(result).toEqual [div0, div1, div2, div3]
199
+
200
+ describe 'up.element.subtree()', ->
201
+
202
+ it 'returns all descendants of the given root matching the given selector', ->
203
+ $element = $fixture('.element')
204
+ $matchingChild = $element.affix('.child.match')
205
+ $matchingGrandChild = $matchingChild.affix('.grand-child.match')
206
+ $otherChild = $element.affix('.child')
207
+ $otherGrandChild = $otherChild.affix('.grand-child')
208
+ results = up.element.subtree($element[0], '.match')
209
+ expect(results).toEqual [$matchingChild[0], $matchingGrandChild[0]]
210
+
211
+ it 'includes the given root if it matches the selector', ->
212
+ $element = $fixture('.element.match')
213
+ $matchingChild = $element.affix('.child.match')
214
+ $matchingGrandChild = $matchingChild.affix('.grand-child.match')
215
+ $otherChild = $element.affix('.child')
216
+ $otherGrandChild = $otherChild.affix('.grand-child')
217
+ results = up.element.subtree($element[0], '.match')
218
+ expect(results).toEqual [$element[0], $matchingChild[0], $matchingGrandChild[0]]
219
+
220
+ it 'does not return ancestors of the root, even if they match', ->
221
+ $parent = $fixture('.parent.match')
222
+ $element = $parent.affix('.element')
223
+ results = up.element.subtree($element[0], '.match')
224
+ expect(results).toEqual []
225
+
226
+ it 'returns an empty list if neither root nor any descendant matches', ->
227
+ $element = $fixture('.element')
228
+ $child = $element.affix('.child')
229
+ results = up.element.subtree($element[0], '.match')
230
+ expect(results).toEqual []
231
+
232
+ it 'supports the custom :has() selector', ->
233
+ $element = $fixture('.selector')
234
+ $childWithSelectorWithChild = $element.affix('.selector')
235
+ $childWithSelectorWithChild.affix('.match')
236
+ $childWithSelectorWithoutChild = $element.affix('.selector')
237
+ $childWithoutSelectorWithChild = $element.affix('.other-selector')
238
+ $childWithoutSelectorWithChild.affix('.match')
239
+ $childWithoutSelectorWithoutChild = $fixture('.other-selector')
240
+
241
+ results = up.element.subtree($element[0], '.selector:has(.match)')
242
+ expect(results).toEqual [$element[0], $childWithSelectorWithChild[0]]
243
+
244
+ describe 'up.element.closest()', ->
245
+
246
+ it 'returns the closest ancestor of the given root that matches the given selector', ->
247
+ $grandGrandMother = $fixture('.match')
248
+ $grandMother = $fixture('.match')
249
+ $mother = $grandMother.affix('.no-match')
250
+ $element = $mother.affix('.element')
251
+
252
+ result = up.element.closest($element[0], '.match')
253
+ expect(result).toBe($grandMother[0])
254
+
255
+ it 'returns the given root if it matches', ->
256
+ $mother = $fixture('.match')
257
+ $element = $mother.affix('.match')
258
+
259
+ result = up.element.closest($element[0], '.match')
260
+ expect(result).toBe($element[0])
261
+
262
+ it 'does not return descendants of the root, even if they match', ->
263
+ $element = $fixture('.element')
264
+ $child = $element.affix('.match')
265
+
266
+ result = up.element.closest($element[0], '.match')
267
+ expect(result).toBeMissing()
268
+
269
+ it 'returns missing if neither root nor ancestor matches', ->
270
+ $mother = $fixture('.no-match')
271
+ $element = $mother.affix('.no-match')
272
+
273
+ result = up.element.closest($element[0], '.match')
274
+ expect(result).toBeMissing()
275
+
276
+ describe 'up.element.ancestor()', ->
277
+
278
+ it 'returns the closest ancestor of the given root that matches the given selector', ->
279
+ $grandGrandMother = $fixture('.match')
280
+ $grandMother = $fixture('.match')
281
+ $mother = $grandMother.affix('.no-match')
282
+ $element = $mother.affix('.element')
283
+
284
+ result = up.element.ancestor($element[0], '.match')
285
+ expect(result).toBe($grandMother[0])
286
+
287
+ it 'does not return the given root, even if it matches', ->
288
+ $element = $fixture('.match')
289
+
290
+ result = up.element.ancestor($element[0], '.match')
291
+ expect(result).toBeMissing()
292
+
293
+ it 'does not return descendants of the root, even if they match', ->
294
+ $element = $fixture('.element')
295
+ $child = $element.affix('.match')
296
+
297
+ result = up.element.ancestor($element[0], '.match')
298
+ expect(result).toBeMissing()
299
+
300
+ it 'returns missing if no ancestor matches', ->
301
+ $mother = $fixture('.no-match')
302
+ $element = $mother.affix('.no-match')
303
+
304
+ result = up.element.ancestor($element[0], '.match')
305
+ expect(result).toBeMissing()
306
+
307
+ describe 'up.element.emit()', ->
308
+
309
+ describe 'up.element.remove()', ->
310
+
311
+ it 'removes the given element from the DOM', ->
312
+ element = fixture('.element')
313
+ expect(element).toBeAttached()
314
+ up.element.remove(element)
315
+ expect(element).toBeDetached()
316
+
317
+ describe 'up.element.toggle()', ->
318
+
319
+ it 'hides the given element if it is visible', ->
320
+ element = fixture('.element')
321
+ up.element.toggle(element)
322
+ expect(element).toBeHidden()
323
+
324
+ it 'shows the given element if it is hidden', ->
325
+ element = fixture('.element', style: { display: 'none' })
326
+ up.element.toggle(element)
327
+ expect(element).toBeVisible()
328
+
329
+ it 'hides the given element if the second argument is false', ->
330
+ element = fixture('.element')
331
+ expect(element).toBeVisible()
332
+ up.element.toggle(element, false)
333
+ expect(element).toBeHidden()
334
+
335
+ it 'shows the given element if the second argument is true', ->
336
+ element = fixture('.element')
337
+ element.style.display = 'none'
338
+ expect(element).toBeHidden()
339
+ up.element.toggle(element, true)
340
+ expect(element).toBeVisible()
341
+
342
+ describe 'up.element.toggleClass()', ->
343
+
344
+ it 'removes a class from an element that has that class', ->
345
+ element = fixture('.klass')
346
+ up.element.toggleClass(element, 'klass')
347
+ expect(element).not.toHaveClass('klass')
348
+
349
+ it 'adds a class to an element that does not have that class', ->
350
+ element = fixture('div')
351
+ up.element.toggleClass(element, 'klass')
352
+ expect(element).toHaveClass('klass')
353
+
354
+ it 'removes a class from the an element if the second argument is false', ->
355
+ element = fixture('.klass')
356
+ up.element.toggleClass(element, 'klass', false)
357
+ expect(element).not.toHaveClass('klass')
358
+
359
+ it 'adds a class to an element if the second argument is true', ->
360
+ element = fixture('div')
361
+ up.element.toggleClass(element, 'klass', true)
362
+ expect(element).toHaveClass('klass')
363
+
364
+ describe 'up.element.createFromSelector()', ->
365
+
366
+ it 'creates an element with a given tag', ->
367
+ element = up.element.createFromSelector('table')
368
+ expect(element).toBeElement()
369
+ expect(element.tagName).toEqual('TABLE')
370
+
371
+ it 'creates a custom element with a dash-separated tag name', ->
372
+ element = up.element.createFromSelector('ta-belle')
373
+ expect(element).toBeElement()
374
+ expect(element.tagName).toEqual('TA-BELLE')
375
+
376
+ it 'creates an element with a given tag and class', ->
377
+ element = up.element.createFromSelector('table.foo')
378
+ expect(element).toBeElement()
379
+ expect(element.tagName).toEqual('TABLE')
380
+ expect(element.className).toEqual('foo')
381
+
382
+ it 'creates an element with multiple classes', ->
383
+ element = up.element.createFromSelector('table.foo.bar')
384
+ expect(element).toBeElement()
385
+ expect(element.tagName).toEqual('TABLE')
386
+ expect(element.className).toEqual('foo bar')
387
+
388
+ it 'creates an element with a given tag and ID', ->
389
+ element = up.element.createFromSelector('table#foo')
390
+ expect(element).toBeElement()
391
+ expect(element.tagName).toEqual('TABLE')
392
+ expect(element.id).toEqual('foo')
393
+
394
+ it 'creates a <div> if no tag is given', ->
395
+ element = up.element.createFromSelector('.foo')
396
+ expect(element).toBeElement()
397
+ expect(element.tagName).toEqual('DIV')
398
+ expect(element.className).toEqual('foo')
399
+
400
+ it 'creates a hierarchy of elements for a descendant selector, and returns the root element', ->
401
+ parent = up.element.createFromSelector('ul.foo li.bar')
402
+ expect(parent).toBeElement()
403
+ expect(parent.tagName).toEqual('UL')
404
+ expect(parent.className).toEqual('foo')
405
+ expect(parent.children.length).toBe(1)
406
+ expect(parent.firstChild.tagName).toEqual('LI')
407
+ expect(parent.firstChild.className).toEqual('bar')
408
+
409
+ it 'creates a hierarchy of elements for a child selector, and returns the root element', ->
410
+ parent = up.element.createFromSelector('ul.foo > li.bar')
411
+ expect(parent).toBeElement()
412
+ expect(parent.tagName).toEqual('UL')
413
+ expect(parent.className).toEqual('foo')
414
+ expect(parent.children.length).toBe(1)
415
+ expect(parent.firstChild.tagName).toEqual('LI')
416
+ expect(parent.firstChild.className).toEqual('bar')
417
+
418
+ it 'creates an element with an attribute', ->
419
+ element = up.element.createFromSelector('ul[foo=bar]')
420
+ expect(element).toBeElement()
421
+ expect(element.tagName).toEqual('UL')
422
+ expect(element.getAttribute('foo')).toEqual('bar')
423
+
424
+ it 'creates an element with a value-less attribute', ->
425
+ element = up.element.createFromSelector('ul[foo]')
426
+ expect(element).toBeElement()
427
+ expect(element.tagName).toEqual('UL')
428
+ expect(element.getAttribute('foo')).toEqual('')
429
+
430
+ it 'creates an element with multiple attributes', ->
431
+ element = up.element.createFromSelector('ul[foo=bar][baz=bam]')
432
+ expect(element).toBeElement()
433
+ expect(element.tagName).toEqual('UL')
434
+ expect(element.getAttribute('foo')).toEqual('bar')
435
+ expect(element.getAttribute('baz')).toEqual('bam')
436
+
437
+ it 'allows to quote attribute values with double quotes', ->
438
+ element = up.element.createFromSelector('ul[foo="bar baz"]')
439
+ expect(element).toBeElement()
440
+ expect(element.tagName).toEqual('UL')
441
+ expect(element.getAttribute('foo')).toEqual('bar baz')
442
+
443
+ it 'allows to quote attribute values with single quotes', ->
444
+ element = up.element.createFromSelector("ul[foo='bar baz']")
445
+ expect(element).toBeElement()
446
+ expect(element.tagName).toEqual('UL')
447
+ expect(element.getAttribute('foo')).toEqual('bar baz')
448
+
449
+ it 'throws an error when encountering an unknown selector', ->
450
+ parse = -> up.element.createFromSelector("ul~baz")
451
+ expect(parse).toThrowError('Cannot parse selector: ul~baz')
452
+
453
+ it 'sets attributes from a second argument', ->
454
+ element = up.element.createFromSelector('div', foo: 'one', bar: 'two')
455
+ expect(element.getAttribute('foo')).toEqual('one')
456
+ expect(element.getAttribute('bar')).toEqual('two')
457
+
458
+ it 'sets inline styles from a { style } option', ->
459
+ element = up.element.createFromSelector('div', style: { fontSize: '100px', marginTop: '200px' })
460
+ expect(element.style.fontSize).toEqual('100px')
461
+ expect(element.style.marginTop).toEqual('200px')
462
+
463
+ it 'adds an addition class from a { class } option', ->
464
+ element = up.element.createFromSelector('.foo', class: 'bar')
465
+ expect(element).toHaveClass('foo')
466
+ expect(element).toHaveClass('bar')
467
+
468
+ it 'sets the element text from a { text } option', ->
469
+ element = up.element.createFromSelector('.foo', text: 'text')
470
+ expect(element).toHaveText('text')
471
+
472
+ it 'escapes HTML from a { text } option', ->
473
+ element = up.element.createFromSelector('.foo', text: '<script>alert("foo")</script>')
474
+ expect(element).toHaveText('<script>alert("foo")</script>')
475
+
476
+ describe 'up.element.affix', ->
477
+
478
+ it 'creates an element from the given selector and attaches it to the given container', ->
479
+ container = fixture('.container')
480
+ element = up.element.affix(container, 'span')
481
+ expect(element.tagName).toEqual('SPAN')
482
+ expect(element.parentElement).toBe(container)
483
+
484
+ describe 'up.element.toSelector', ->
485
+
486
+ it "prefers using the element's 'up-id' attribute to using the element's ID", ->
487
+ $element = $fixture('div[up-id=up-id-value]#id-value')
488
+ expect(up.element.toSelector($element)).toBe('[up-id="up-id-value"]')
489
+
490
+ it "prefers using the element's ID to using the element's name", ->
491
+ $element = $fixture('div#id-value[name=name-value]')
492
+ expect(up.element.toSelector($element)).toBe("#id-value")
493
+
494
+ it "selects the ID with an attribute selector if the ID contains a slash", ->
495
+ $element = $fixture('div').attr(id: 'foo/bar')
496
+ expect(up.element.toSelector($element)).toBe('[id="foo/bar"]')
497
+
498
+ it "selects the ID with an attribute selector if the ID contains a space", ->
499
+ $element = $fixture('div').attr(id: 'foo bar')
500
+ expect(up.element.toSelector($element)).toBe('[id="foo bar"]')
501
+
502
+ it "selects the ID with an attribute selector if the ID contains a dot", ->
503
+ $element = $fixture('div').attr(id: 'foo.bar')
504
+ expect(up.element.toSelector($element)).toBe('[id="foo.bar"]')
505
+
506
+ it "selects the ID with an attribute selector if the ID contains a quote", ->
507
+ $element = $fixture('div').attr(id: 'foo"bar')
508
+ expect(up.element.toSelector($element)).toBe('[id="foo\\"bar"]')
509
+
510
+ it "prefers using the element's tagName + [name] to using the element's classes", ->
511
+ $element = $fixture('input[name=name-value].class1.class2')
512
+ expect(up.element.toSelector($element)).toBe('input[name="name-value"]')
513
+
514
+ it "prefers using the element's classes to using the element's ARIA label", ->
515
+ $element = $fixture('div.class1.class2[aria-label="ARIA label value"]')
516
+ expect(up.element.toSelector($element)).toBe(".class1.class2")
517
+
518
+ it 'does not use Unpoly classes to compose a class selector', ->
519
+ $element = $fixture('div.class1.up-current.class2')
520
+ expect(up.element.toSelector($element)).toBe(".class1.class2")
521
+
522
+ it "prefers using the element's ARIA label to using the element's tag name", ->
523
+ $element = $fixture('div[aria-label="ARIA label value"]')
524
+ expect(up.element.toSelector($element)).toBe('[aria-label="ARIA label value"]')
525
+
526
+ it "uses the element's tag name if no better description is available", ->
527
+ $element = $fixture('div')
528
+ expect(up.element.toSelector($element)).toBe("div")
529
+
530
+ it 'escapes quotes in attribute selector values', ->
531
+ $element = $fixture('div')
532
+ $element.attr('aria-label', 'foo"bar')
533
+ expect(up.element.toSelector($element)).toBe('[aria-label="foo\\"bar"]')
534
+
535
+ describe 'up.element.createDocumentFromHtml', ->
536
+
537
+ it 'parses a string that contains a serialized HTML document', ->
538
+ string = """
539
+ <html lang="foo">
540
+ <head>
541
+ <title>document title</title>
542
+ </head>
543
+ <body data-env='production'>
544
+ <div>line 1</div>
545
+ <div>line 2</div>
546
+ </body>
547
+ </html>
548
+ """
549
+
550
+ element = up.element.createDocumentFromHtml(string)
551
+
552
+ expect(element.querySelector('head title').textContent).toEqual('document title')
553
+ expect(element.querySelector('body').getAttribute('data-env')).toEqual('production')
554
+ expect(element.querySelectorAll('body div').length).toBe(2)
555
+ expect(element.querySelectorAll('body div')[0].textContent).toEqual('line 1')
556
+ expect(element.querySelectorAll('body div')[1].textContent).toEqual('line 2')
557
+
558
+ it 'parses a string that contains carriage returns (bugfix)', ->
559
+ string = """
560
+ <html>\r
561
+ <body>\r
562
+ <div>line</div>\r
563
+ </body>\r
564
+ </html>\r
565
+ """
566
+
567
+ $element = up.element.createDocumentFromHtml(string)
568
+ expect($element.querySelector('body')).toBeGiven()
569
+ expect($element.querySelector('body div').textContent).toEqual('line')
570
+
571
+ it 'does not run forever if a page has a <head> without a <title> (bugfix)', ->
572
+ html = """
573
+ <!doctype html>
574
+ <html>
575
+ <head>
576
+ <meta charset="utf-8" />
577
+ <meta name="format-detection" content="telephone=no">
578
+ <link href='/images/favicon.png' rel='shortcut icon' type='image/png'>
579
+ <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'>
580
+
581
+ <base href="/examples/update-fragment/" />
582
+ <link href='http://fonts.googleapis.com/css?family=Orbitron:400|Ubuntu+Mono:400,700|Source+Sans+Pro:300,400,700,400italic,700italic' rel='stylesheet' type='text/css'>
583
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
584
+ <link href="/stylesheets/example/all.css" rel="stylesheet" />
585
+ <script src="/javascripts/example.js"></script>
586
+ </head>
587
+ <body>
588
+ <div class="page">
589
+ <div class="story">
590
+
591
+ <h1>Full story</h1>
592
+ <p>Lorem ipsum dolor sit amet.</p>
593
+
594
+ <a href="preview.html" up-target=".story">
595
+ Read summary
596
+ </a>
597
+ </div>
598
+
599
+ </div>
600
+ </body>
601
+ </html>
602
+ """
603
+ element = up.element.createDocumentFromHtml(html)
604
+ expect(element.querySelector("title")).toBeMissing()
605
+ expect(element.querySelector("h1").textContent).toEqual('Full story')
606
+
607
+ it 'can parse HTML without a <head>', ->
608
+ html = """
609
+ <html>
610
+ <body>
611
+ <h1>Full story</h1>
612
+ </body>
613
+ </html>
614
+ """
615
+ element = up.element.createDocumentFromHtml(html)
616
+ expect(element.querySelector("title")).toBeMissing()
617
+ expect(element.querySelector("h1").textContent).toEqual('Full story')
618
+
619
+ it 'can parse a HTML fragment without a <body>', ->
620
+ html = """
621
+ <h1>Full story</h1>
622
+ """
623
+ element = up.element.createDocumentFromHtml(html)
624
+ expect(element.querySelector("title")).toBeMissing()
625
+ expect(element.querySelector("h1").textContent).toEqual('Full story')
626
+
627
+ describe 'up.element.createFromHtml', ->
628
+
629
+ it 'creates an element from the given HTML fragment', ->
630
+ html = """
631
+ <h1>Full story</h1>
632
+ """
633
+ element = up.element.createFromHtml(html)
634
+ expect(element.tagName).toEqual('H1')
635
+ expect(element.textContent).toEqual('Full story')
636
+
637
+ describe 'up.element.fixedToAbsolute', ->
638
+
639
+ it "changes the given element's position from fixed to absolute, without changing its rendered position", ->
640
+ $container = $fixture('.container').css
641
+ position: 'absolute'
642
+ left: '100px'
643
+ top: '100px'
644
+ backgroundColor: 'yellow'
645
+
646
+ $element = $container.affix('.element').text('element').css
647
+ position: 'fixed',
648
+ left: '70px'
649
+ top: '30px'
650
+ backgroundColor: 'red'
651
+
652
+ oldRect = up.Rect.fromElement($element[0])
653
+
654
+ up.element.fixedToAbsolute($element[0])
655
+
656
+ expect($element.css('position')).toEqual('absolute')
657
+ newRect = up.Rect.fromElement($element[0])
658
+ expect(newRect).toEqual(oldRect)
659
+
660
+ it 'correctly positions an element with margins', ->
661
+ $container = $fixture('.container').css
662
+ position: 'absolute'
663
+ left: '20px'
664
+ top: '20px'
665
+ backgroundColor: 'yellow'
666
+
667
+ $element = $container.affix('.element').text('element').css
668
+ position: 'fixed',
669
+ left: '40px'
670
+ top: '60px'
671
+ backgroundColor: 'red'
672
+ margin: '15px'
673
+
674
+ oldRect = up.Rect.fromElement($element[0])
675
+
676
+ up.element.fixedToAbsolute($element[0])
677
+
678
+ expect($element.css('position')).toEqual('absolute')
679
+ newRect = up.Rect.fromElement($element[0])
680
+ expect(newRect).toEqual(oldRect)
681
+
682
+ it 'correctly positions an element when its new offset parent has margins', ->
683
+ $container = $fixture('.container').css
684
+ position: 'absolute'
685
+ left: '100px'
686
+ top: '100px'
687
+ margin: '15px',
688
+ backgroundColor: 'yellow'
689
+
690
+ $element = $container.affix('.element').text('element').css
691
+ position: 'fixed',
692
+ left: '70px'
693
+ top: '30px'
694
+ backgroundColor: 'red'
695
+
696
+ oldRect = up.Rect.fromElement($element[0])
697
+
698
+ up.element.fixedToAbsolute($element[0])
699
+
700
+ expect($element.css('position')).toEqual('absolute')
701
+ newRect = up.Rect.fromElement($element[0])
702
+ expect(newRect).toEqual(oldRect)
703
+
704
+ it 'correctly positions an element when the new offset parent is scrolled', ->
705
+ $container = $fixture('.container').css
706
+ position: 'absolute'
707
+ left: '100px'
708
+ top: '100px'
709
+ margin: '15px',
710
+ backgroundColor: 'yellow'
711
+ overflowY: 'scroll'
712
+
713
+ $staticContainerContent = $container.affix('.content').css(height: '3000px').scrollTop(100)
714
+
715
+ $element = $container.affix('.element').text('element').css
716
+ position: 'fixed',
717
+ left: '70px'
718
+ top: '30px'
719
+ backgroundColor: 'red'
720
+
721
+ oldRect = up.Rect.fromElement($element[0])
722
+
723
+ up.element.fixedToAbsolute($element[0])
724
+
725
+ expect($element.css('position')).toEqual('absolute')
726
+ newRect = up.Rect.fromElement($element[0])
727
+ expect(newRect).toEqual(oldRect)
728
+
729
+ describe 'up.element.booleanAttr', ->
730
+
731
+ it 'returns true if the attribute value is the string "true"', ->
732
+ element = fixture('div[foo=true]')
733
+ expect(up.element.booleanAttr(element, 'foo')).toBe(true)
734
+
735
+ it 'returns true if the attribute value is the name of the attribute', ->
736
+ element = fixture('div[foo=foo]')
737
+ expect(up.element.booleanAttr(element, 'foo')).toBe(true)
738
+
739
+ it 'returns false if the attribute value is the string "false"', ->
740
+ element = fixture('div[foo=false]')
741
+ expect(up.element.booleanAttr(element, 'foo')).toBe(false)
742
+
743
+ it 'returns a missing value if the element has no such attribute', ->
744
+ element = fixture('div')
745
+ expect(up.element.booleanAttr(element, 'foo')).toBeMissing()
746
+
747
+ it 'returns undefined if the attribute value cannot be cast to a boolean', ->
748
+ element = fixture('div[foo="some text"]')
749
+ expect(up.element.booleanAttr(element, 'foo')).toBeUndefined()
750
+
751
+ describe 'up.element.setTemporaryStyle', ->
752
+
753
+ it "sets the given inline styles and returns a function that will restore the previous inline styles", ->
754
+ div = fixture('div[style="color: red"]')
755
+ restore = up.element.setTemporaryStyle(div, { color: 'blue' })
756
+ expect(div.getAttribute('style')).toContain('color: blue')
757
+ expect(div.getAttribute('style')).not.toContain('color: red')
758
+ restore()
759
+ expect(div.getAttribute('style')).not.toContain('color: blue')
760
+ expect(div.getAttribute('style')).toContain('color: red')
761
+
762
+ it "does not restore inherited styles", ->
763
+ div = fixture('div[class="red-background"]')
764
+ restore = up.element.setTemporaryStyle(div, { backgroundColor: 'blue' })
765
+ expect(div.getAttribute('style')).toContain('background-color: blue')
766
+ restore()
767
+ expect(div.getAttribute('style')).not.toContain('background-color')
768
+
769
+ describe 'up.element.inlineStyle', ->
770
+
771
+ describe 'with a string as second argument', ->
772
+
773
+ it 'returns a CSS value string from an inline [style] attribute', ->
774
+ div = $fixture('div').attr('style', 'background-color: #ff0000')[0]
775
+ style = up.element.inlineStyle(div, 'backgroundColor')
776
+ # Browsers convert colors to rgb() values, even IE11
777
+ expect(style).toEqual('rgb(255, 0, 0)')
778
+
779
+ it 'returns a blank value if the element does not have the given property in the [style] attribute', ->
780
+ div = $fixture('div').attr('style', 'background-color: red')[0]
781
+ style = up.element.inlineStyle(div, 'color')
782
+ expect(style).toBeBlank()
783
+
784
+ it 'returns a blank value the given property is a computed property, but not in the [style] attribute', ->
785
+ div = $fixture('div[class="red-background"]')[0]
786
+ inlineStyle = up.element.inlineStyle(div, 'backgroundColor')
787
+ computedStyle = up.element.style(div, 'backgroundColor')
788
+ expect(computedStyle).toEqual('rgb(255, 0, 0)')
789
+ expect(inlineStyle).toBeBlank()
790
+
791
+ describe 'with an array as second argument', ->
792
+
793
+ it 'returns an object with the given inline [style] properties', ->
794
+ div = $fixture('div').attr('style', 'background-color: #ff0000; color: #0000ff')[0]
795
+ style = up.element.inlineStyle(div, ['backgroundColor', 'color'])
796
+ expect(style).toEqual
797
+ backgroundColor: 'rgb(255, 0, 0)'
798
+ color: 'rgb(0, 0, 255)'
799
+
800
+ it 'returns blank keys if the element does not have the given property in the [style] attribute', ->
801
+ div = $fixture('div').attr('style', 'background-color: #ff0000')[0]
802
+ style = up.element.inlineStyle(div, ['backgroundColor', 'color'])
803
+ expect(style).toHaveOwnProperty('color')
804
+ expect(style.color).toBeBlank()
805
+
806
+ it 'returns a blank value the given property is a computed property, but not in the [style] attribute', ->
807
+ div = fixture('div[class="red-background"]')
808
+ inlineStyleHash = up.element.inlineStyle(div, ['backgroundColor'])
809
+ computedBackground = up.element.style(div, 'backgroundColor')
810
+ expect(computedBackground).toEqual('rgb(255, 0, 0)')
811
+ expect(inlineStyleHash).toHaveOwnProperty('backgroundColor')
812
+ expect(inlineStyleHash.backgroundColor).toBeBlank()
813
+
814
+ describe 'up.element.setStyle', ->
815
+
816
+ it "sets the given style properties as the given element's [style] attribute", ->
817
+ div = fixture('div')
818
+ up.element.setStyle(div, { color: 'red', 'background-color': 'blue' })
819
+ style = div.getAttribute('style')
820
+ expect(style).toContain('color: red')
821
+ expect(style).toContain('background-color: blue')
822
+
823
+ it "merges the given style properties into the given element's existing [style] value", ->
824
+ div = fixture('div[style="color: red"]')
825
+ up.element.setStyle(div, { 'background-color': 'blue' })
826
+ style = div.getAttribute('style')
827
+ expect(style).toContain('color: red')
828
+ expect(style).toContain('background-color: blue')
829
+
830
+ it "converts the values of known length properties to px values automatically (with kebab-case)", ->
831
+ div = fixture('div')
832
+ up.element.setStyle(div, { 'padding-top': 100 })
833
+ style = div.getAttribute('style')
834
+ expect(style).toContain('padding-top: 100px')
835
+
836
+ it "converts the values of known length properties to px values automatically (with camelCase)", ->
837
+ div = fixture('div')
838
+ up.element.setStyle(div, { 'paddingTop': 100 })
839
+ style = div.getAttribute('style')
840
+ expect(style).toContain('padding-top: 100px')
841
+
842
+ it "accepts CSS property names in camelCase", ->
843
+ div = fixture('div')
844
+ up.element.setStyle(div, { 'backgroundColor': 'blue' })
845
+ style = div.getAttribute('style')
846
+ expect(style).toContain('background-color: blue')
847
+
848
+ describe 'up.element.style', ->
849
+
850
+ it 'returns the computed style for the given CSS property in kebab-case', ->
851
+ div = fixture('div')
852
+ div.style.paddingTop = '10px'
853
+ value = up.element.style(div, 'padding-top')
854
+ expect(value).toEqual('10px')
855
+
856
+ it 'returns the computed style for the given CSS property in camelCase', ->
857
+ div = fixture('div')
858
+ div.style.paddingTop = '10px'
859
+ value = up.element.style(div, 'paddingTop')
860
+ expect(value).toEqual('10px')
861
+
862
+ it 'returns the computed style for multiple CSS properties in kebab-case', ->
863
+ div = fixture('div')
864
+ div.style.paddingTop = '10px'
865
+ div.style.paddingBottom = '20px'
866
+ value = up.element.style(div, ['padding-top', 'padding-bottom'])
867
+ expect(value).toEqual { 'padding-top': '10px', 'padding-bottom': '20px' }
868
+
869
+ it 'returns the computed style for multiple CSS properties in camelCase', ->
870
+ div = fixture('div')
871
+ div.style.paddingTop = '10px'
872
+ div.style.paddingBottom = '20px'
873
+ value = up.element.style(div, ['paddingTop', 'paddingBottom'])
874
+ expect(value).toEqual { 'paddingTop': '10px', 'paddingBottom': '20px' }
875
+
876
+ describe 'up.element.isVisible', ->
877
+
878
+ it 'returns true for an attached element', ->
879
+ element = fixture('.element', text: 'content')
880
+ expect(up.element.isVisible(element)).toBe(true)
881
+
882
+ it 'returns true for an attached block element without content', ->
883
+ element = fixture('div.element')
884
+ expect(up.element.isVisible(element)).toBe(true)
885
+
886
+ it 'returns true for an attached inline element without content', ->
887
+ element = fixture('span.element')
888
+ expect(up.element.isVisible(element)).toBe(true)
889
+
890
+ it 'returns false for a detached element', ->
891
+ element = document.createElement('text')
892
+ element.innerText = 'content'
893
+ expect(up.element.isVisible(element)).toBe(false)
894
+
895
+ it 'returns false for an attached element with { display: none }', ->
896
+ element = fixture('.element', text: 'content', style: { display: 'none' })
897
+ expect(up.element.isVisible(element)).toBe(false)