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.Cache', ->
2
5
 
3
6
  describe '#get', ->
@@ -0,0 +1,24 @@
1
+ describe 'up.Config', ->
2
+
3
+ describe 'constructor', ->
4
+
5
+ it 'creates an object with the given attributes', ->
6
+ object = new up.Config(a: 1, b: 2)
7
+ expect(object.a).toBe(1)
8
+ expect(object.b).toBe(2)
9
+
10
+ describe '#reset', ->
11
+
12
+ it 'resets the object to its original state', ->
13
+ object = new up.Config(a: 1)
14
+ expect(object.b).toBeUndefined()
15
+ object.a = 2
16
+ expect(object.a).toBe(2)
17
+ object.reset()
18
+ expect(object.a).toBe(1)
19
+
20
+ it 'does not remove the #reset or #update method from the object', ->
21
+ object = new up.Config(a: 1)
22
+ object.b = 2
23
+ object.reset()
24
+ expect(object.reset).toBeDefined()
@@ -0,0 +1,45 @@
1
+ u = up.util
2
+
3
+ describe 'up.DivertibleChain', ->
4
+
5
+ describe '#asap', ->
6
+
7
+ it "cancels all unstarted tasks, waits for the current task and starts the given task", (done) ->
8
+ chain = new up.DivertibleChain()
9
+
10
+ timer1Spy = jasmine.createSpy('timer1')
11
+ timer1 = ->
12
+ timer1Spy()
13
+ up.specUtil.promiseTimer(100) # delay execution of next timer
14
+
15
+ timer2Spy = jasmine.createSpy('timer2')
16
+ timer2 = ->
17
+ timer2Spy()
18
+ up.specUtil.promiseTimer(100) # delay execution of next timer
19
+
20
+ timer3Spy = jasmine.createSpy('timer3')
21
+ timer3 = ->
22
+ timer3Spy()
23
+ up.specUtil.promiseTimer(100) # delay execution of next timer
24
+
25
+ timer4Spy = jasmine.createSpy('timer4')
26
+ timer4 = ->
27
+ timer4Spy()
28
+ up.specUtil.promiseTimer(100) # delay execution of next timer
29
+
30
+ chain.asap(timer1)
31
+ u.task ->
32
+ expect(timer1Spy).toHaveBeenCalled()
33
+ chain.asap(timer2)
34
+ u.task ->
35
+ # timer2 is still waiting for timer1 to finish
36
+ expect(timer2Spy).not.toHaveBeenCalled()
37
+ # Override the (2..n)th tasks. This unschedules timer2.
38
+ chain.asap(timer3, timer4)
39
+ u.timer 150, ->
40
+ expect(timer2Spy).not.toHaveBeenCalled() # Has been canceled
41
+ expect(timer3Spy).toHaveBeenCalled() # timer3 overrode timer2
42
+ expect(timer4Spy).not.toHaveBeenCalled()
43
+ u.timer 150, ->
44
+ expect(timer4Spy).toHaveBeenCalled()
45
+ done()
@@ -1,3 +1,6 @@
1
+ #u = up.util
2
+ #$ = jQuery
3
+ #
1
4
  #describe 'up.FocusTracker', ->
2
5
  #
3
6
  # describe '#lastField', ->
@@ -8,14 +11,14 @@
8
11
  #
9
12
  # it 'returns a <input type="text"> after it was focused', ->
10
13
  # tracker = new up.FocusTracker()
11
- # $form = affix('form')
14
+ # $form = $fixture('form')
12
15
  # $input = $form.affix('input[type=text]')
13
16
  # $input.focus()
14
17
  # expect(tracker.lastField()).toEqual($input[0])
15
18
  #
16
19
  # it 'returns a <select> after it was focused', ->
17
20
  # tracker = new up.FocusTracker()
18
- # $form = affix('form')
21
+ # $form = $fixture('form')
19
22
  # $select = $form.affix('select')
20
23
  # $option = $select.affix('option')
21
24
  # $select.focus()
@@ -0,0 +1,557 @@
1
+ u = up.util
2
+ $ = jQuery
3
+
4
+ describe 'up.Params', ->
5
+
6
+ encodeBrackets = (str) ->
7
+ str = str.replace(/\[/g, '%5B')
8
+ str = str.replace(/\]/g, '%5D')
9
+ str
10
+
11
+ beforeEach ->
12
+ jasmine.addMatchers
13
+ toEqualAfterEncodingBrackets: (util, customEqualityTesters) ->
14
+ compare: (actual, expected) ->
15
+ pass: actual == encodeBrackets(expected)
16
+
17
+ toObject = (value) -> new up.Params(value).toObject()
18
+ toQuery = (value) -> new up.Params(value).toQuery()
19
+ toArray = (value) -> new up.Params(value).toArray()
20
+ toFormData = (value) -> new up.Params(value).toFormData()
21
+
22
+ describe '#toQuery', ->
23
+
24
+ encodedSpace = '%20'
25
+
26
+ it 'returns the query section for the given object', ->
27
+ string = toQuery('foo-key': 'foo value', 'bar-key': 'bar value')
28
+ expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
29
+
30
+ it 'returns the query section for the given array with { name } and { value } keys', ->
31
+ string = toQuery([
32
+ { name: 'foo-key', value: 'foo value' },
33
+ { name: 'bar-key', value: 'bar value' }
34
+ ])
35
+ expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
36
+
37
+ it 'returns a given query string', ->
38
+ string = toQuery('foo=bar')
39
+ expect(string).toEqual('foo=bar')
40
+
41
+ it 'returns an empty string for an empty object', ->
42
+ string = toQuery({})
43
+ expect(string).toEqual('')
44
+
45
+ it 'returns an empty string for an empty string', ->
46
+ string = toQuery('')
47
+ expect(string).toEqual('')
48
+
49
+ it 'returns an empty string for undefined', ->
50
+ string = toQuery(undefined)
51
+ expect(string).toEqual('')
52
+
53
+ it 'URL-encodes characters in the key and value', ->
54
+ string = toQuery({ 'äpfel': 'bäume' })
55
+ expect(string).toEqual('%C3%A4pfel=b%C3%A4ume')
56
+
57
+ it "sets a blank value after the equal sign if a key's value is a blank string", ->
58
+ string = toQuery({'foo': ''})
59
+ expect(string).toEqual('foo=')
60
+
61
+ it 'omits non-primitive values (like Files) from the given params', ->
62
+ # I would like to construct a File, but IE11 does not support the constructor
63
+ blob = new Blob([])
64
+ string = toQuery(string: 'foo', blob: blob)
65
+ expect(string).toEqual('string=foo')
66
+
67
+ it "omits an equal sign if a key's value is null", ->
68
+ string = toQuery({'foo': null})
69
+ expect(string).toEqual('foo')
70
+
71
+ it 'URL-encodes plus characters', ->
72
+ string = toQuery({ 'my+key': 'my+value' })
73
+ expect(string).toEqual('my%2Bkey=my%2Bvalue')
74
+
75
+ # describeCapability 'canInspectFormData', ->
76
+ #
77
+ # it 'converts a FormData object to a query string', ->
78
+ # fd = new FormData()
79
+ # fd.append('key1', 'value1')
80
+ # fd.append('key2', 'value2')
81
+ # string = toQuery(fd)
82
+ # expect(string).toEqual('key1=value1&key2=value2')
83
+
84
+ describe '#toArray', ->
85
+
86
+ it 'normalized null to an empty array', ->
87
+ array = toArray(null)
88
+ expect(array).toEqual([])
89
+
90
+ it 'normalized undefined to an empty array', ->
91
+ array = toArray(undefined)
92
+ expect(array).toEqual([])
93
+
94
+ it 'normalizes an object hash to an array of objects with { name } and { value } keys', ->
95
+ array = toArray(
96
+ 'foo-key': 'foo-value'
97
+ 'bar-key': 'bar-value'
98
+ )
99
+ expect(array).toEqual([
100
+ { name: 'foo-key', value: 'foo-value' },
101
+ { name: 'bar-key', value: 'bar-value' },
102
+ ])
103
+
104
+ it 'builds multiple entries if the given object has array values', ->
105
+ array = toArray(
106
+ foo: ['1', '2']
107
+ bar: '3'
108
+ )
109
+ expect(array).toEqual([
110
+ { name: 'foo', value: '1' },
111
+ { name: 'foo', value: '2' },
112
+ { name: 'bar', value: '3' },
113
+ ])
114
+
115
+ it 'returns a given array without modification', ->
116
+ array = toArray([
117
+ { name: 'foo-key', value: 'foo-value' },
118
+ { name: 'bar-key', value: 'bar-value' },
119
+ ])
120
+ expect(array).toEqual([
121
+ { name: 'foo-key', value: 'foo-value' },
122
+ { name: 'bar-key', value: 'bar-value' },
123
+ ])
124
+
125
+ it 'does not URL-encode special characters in keys or values', ->
126
+ array = toArray(
127
+ 'äpfel': 'bäume'
128
+ )
129
+ expect(array).toEqual([
130
+ { name: 'äpfel', value: 'bäume' },
131
+ ])
132
+
133
+ it 'does not URL-encode spaces in keys or values', ->
134
+ array = toArray(
135
+ 'my key': 'my value'
136
+ )
137
+ expect(array).toEqual([
138
+ { name: 'my key', value: 'my value' },
139
+ ])
140
+
141
+ it 'does not URL-encode ampersands in keys or values', ->
142
+ array = toArray(
143
+ 'my&key': 'my&value'
144
+ )
145
+ expect(array).toEqual([
146
+ { name: 'my&key', value: 'my&value' },
147
+ ])
148
+
149
+ it 'does not URL-encode equal signs in keys or values', ->
150
+ array = toArray(
151
+ 'my=key': 'my=value'
152
+ )
153
+ expect(array).toEqual([
154
+ { name: 'my=key', value: 'my=value' },
155
+ ])
156
+
157
+ # describeCapability 'canInspectFormData', ->
158
+ #
159
+ # it 'converts a FormData object to an array', ->
160
+ # fd = new FormData()
161
+ # fd.append('key1', 'value1')
162
+ # fd.append('key2', 'value2')
163
+ # array = toArray(fd)
164
+ # expect(array).toEqual([
165
+ # { name: 'key1', value: 'value1' },
166
+ # { name: 'key2', value: 'value2' },
167
+ # ])
168
+
169
+
170
+ describe '#toFormData', ->
171
+
172
+ describeCapability 'canInspectFormData', ->
173
+
174
+ it 'converts undefined to an empty FormData object', ->
175
+ params = undefined
176
+ formData = toFormData(params)
177
+ expect(toArray(formData)).toEqual []
178
+
179
+ it 'converts null to an empty FormData object', ->
180
+ params = null
181
+ formData = toFormData(params)
182
+ expect(toArray(formData)).toEqual []
183
+
184
+ it 'converts an object to a FormData object', ->
185
+ params = {
186
+ key1: 'value1',
187
+ key2: 'value2'
188
+ }
189
+ formData = toFormData(params)
190
+ expect(toArray(formData)).toEqual [
191
+ { name: 'key1', value: 'value1' },
192
+ { name: 'key2', value: 'value2' },
193
+ ]
194
+
195
+ it 'returns a FormData with the same values', ->
196
+ input = new FormData()
197
+ input.append('foo', 'bar')
198
+
199
+ formData = toFormData(input)
200
+ expect(formData.get('foo')).toEqual('bar')
201
+
202
+ it 'converts a query string to a FormData object', ->
203
+ params = 'key1=value1&key2=value2'
204
+ formData = toFormData(params)
205
+ expect(toArray(formData)).toEqual [
206
+ { name: 'key1', value: 'value1' },
207
+ { name: 'key2', value: 'value2' },
208
+ ]
209
+
210
+ it 'unescapes percent-encoded characters from a query string', ->
211
+ params = 'my%20key=my%20value'
212
+ formData = toFormData(params)
213
+ expect(toArray(formData)).toEqual [
214
+ { name: 'my key', value: 'my value' }
215
+ ]
216
+
217
+
218
+ describe '#toObject', ->
219
+
220
+ it "parses a query string of flat key/value pairs", ->
221
+ expect(toObject("xfoo")).toEqual("xfoo": null)
222
+ expect(toObject("foo=")).toEqual("foo": "")
223
+ expect(toObject("foo=bar")).toEqual("foo": "bar")
224
+ expect(toObject("foo=\"bar\"")).toEqual("foo": "\"bar\"")
225
+
226
+ expect(toObject("foo=bar&foo=quux")).toEqual("foo": "quux")
227
+ expect(toObject("foo&foo=")).toEqual("foo": "")
228
+ expect(toObject("foo=1&bar=2")).toEqual("foo": "1", "bar": "2")
229
+ expect(toObject("&foo=1&&bar=2")).toEqual("foo": "1", "bar": "2")
230
+ expect(toObject("foo&bar=")).toEqual("foo": null, "bar": "")
231
+ expect(toObject("foo=bar&baz=")).toEqual("foo": "bar", "baz": "")
232
+
233
+ it 'URL-decodes keys and values', ->
234
+ expect(toObject("my%20weird%20field=q1%212%22%27w%245%267%2Fz8%29%3F")).toEqual("my weird field": "q1!2\"'w$5&7/z8)?")
235
+ expect(toObject("a=b&pid%3D1234=1023")).toEqual("pid=1234": "1023", "a": "b")
236
+ # expect(-> up.params.toObject("foo%81E=1")).toThrowError() # invalid byte sequence in UTF-8
237
+
238
+ it 'keeps the last value if the same key appears multiple time in the input', ->
239
+ expect(toObject("foo=1&bar=2&foo=3")).toEqual("foo": "3", "bar": "2")
240
+
241
+ it 'builds an array value if the key ends in "[]"', ->
242
+ expect(toObject("foo[]=1&bar=2&foo[]=3")).toEqual("foo[]": ["1", "3"], "bar": "2")
243
+
244
+ it 'ignores keys that would overwrite an Object prototype property', ->
245
+ obj = toObject("foo=bar&hasOwnProperty=baz")
246
+ expect(obj['foo']).toEqual('bar')
247
+ expect(u.isFunction obj['hasOwnProperty']).toBe(true)
248
+
249
+ describe '#add', ->
250
+
251
+ it 'adds a single key and value', ->
252
+ params = new up.Params(foo: 'one')
253
+ params.add('bar', 'two')
254
+ expect(params.toObject()).toEqual { foo: 'one', bar: 'two' }
255
+
256
+ it 'stores multiple entries with the same name', ->
257
+ params = new up.Params(foo: 'one')
258
+ params.add('foo', 'two')
259
+ expect(params.toArray()).toEqual [
260
+ { name: 'foo', value: 'one' },
261
+ { name: 'foo', value: 'two' }
262
+ ]
263
+
264
+ describe '#get', ->
265
+
266
+ describe '(with object)', ->
267
+
268
+ it 'returns the value for the given name', ->
269
+ params = new up.Params(foo: 'one', bar: 'two')
270
+ value = params.get('bar')
271
+ expect(value).toEqual('two')
272
+
273
+ it 'returns undefined if no value is set for the given name', ->
274
+ params = new up.Params(foo: 'one')
275
+ value = params.get('bar')
276
+ expect(value).toBeUndefined()
277
+
278
+ it 'returns undefined for names that are also a basic object property', ->
279
+ params = new up.Params({})
280
+ value = params.get('hasOwnProperty')
281
+ expect(value).toBeUndefined()
282
+
283
+ it 'returns the first matching entries if there are are multiple entries with the same name', ->
284
+ params = new up.Params('foo=one&foo=two')
285
+ value = params.get('foo')
286
+ expect(value).toEqual('one')
287
+
288
+ describe '#getAll', ->
289
+
290
+ it 'returns all entries with the given name'
291
+
292
+ describe '#delete', ->
293
+
294
+ it 'deletes all entries with the given name'
295
+
296
+ describe '#set', ->
297
+
298
+ it 'adds an entry with the given name and value'
299
+
300
+ it 'deletes any previous entries with the same name'
301
+
302
+ describe '#toURL', ->
303
+
304
+ it 'composes a URL from a base URL (without query section) and a query section', ->
305
+ base = 'http://foo.bar/path'
306
+ params = new up.Params('key=value')
307
+ expect(params.toURL(base)).toEqual('http://foo.bar/path?key=value')
308
+
309
+ it 'accepts other forms of params (instead of query sections)', ->
310
+ base = 'http://foo.bar/path'
311
+ params = new up.Params(key: 'value')
312
+ expect(params.toURL(base)).toEqual('http://foo.bar/path?key=value')
313
+
314
+ it 'adds more params to a base URL that already has a query section', ->
315
+ base = 'http://foo.bar/path?key1=value1'
316
+ params = new up.Params(key2: 'value2')
317
+ expect(params.toURL(base)).toEqual('http://foo.bar/path?key1=value1&key2=value2')
318
+
319
+ it 'does not add a question mark to the base URL if the given params are blank', ->
320
+ base = 'http://foo.bar/path'
321
+ params = new up.Params([])
322
+ expect(params.toURL(base)).toEqual('http://foo.bar/path')
323
+
324
+ describe '.fromURL', ->
325
+
326
+ it 'parses the query section from an URL, without leading question mark', ->
327
+ url = 'http://foo.bar/path?key=value'
328
+ params = up.Params.fromURL(url)
329
+ expect(params.toQuery()).toEqual('key=value')
330
+
331
+ it 'parses no params if the URL has no query section', ->
332
+ url = 'http://foo.bar/path'
333
+ params = up.Params.fromURL(url)
334
+ expect(params.toArray()).toEqual([])
335
+
336
+ describe '.fromForm', ->
337
+
338
+ it 'serializes a form with multiple inputs', ->
339
+ $form = $fixture('form')
340
+ $form.append('<input name="key1" value="value1">')
341
+ $form.append('<input name="key2" value="value2">')
342
+
343
+ params = up.Params.fromForm($form)
344
+ expect(params.toArray()).toEqual [
345
+ { name: 'key1', value: 'value1' },
346
+ { name: 'key2', value: 'value2' },
347
+ ]
348
+
349
+ it 'serializes an <input type="text"> with its default [value]', ->
350
+ $form = $fixture('form')
351
+ $form.append('<input type="text" name="key" value="value-from-attribute">')
352
+
353
+ params = up.Params.fromForm($form)
354
+ expect(params.toArray()).toEqual [
355
+ { name: 'key', value: 'value-from-attribute' }
356
+ ]
357
+
358
+ it 'serializes an <input type="text"> that had its value property changed by a script', ->
359
+ $form = $fixture('form')
360
+ $input = $('<input type="text" name="key" value="value-from-attribute">').appendTo($form)
361
+ $input[0].value = 'value-from-script'
362
+
363
+ params = up.Params.fromForm($form)
364
+ expect(params.toArray()).toEqual [
365
+ { name: 'key', value: 'value-from-script' }
366
+ ]
367
+
368
+ it 'serializes an <input type="hidden"> with its default [value]', ->
369
+ $form = $fixture('form')
370
+ $form.append('<input type="hidden" name="key" value="value-from-attribute">')
371
+
372
+ params = up.Params.fromForm($form)
373
+ expect(params.toArray()).toEqual [
374
+ { name: 'key', value: 'value-from-attribute' }
375
+ ]
376
+
377
+ it 'serializes an <input type="hidden"> that had its value property changed by a script', ->
378
+ $form = $fixture('form')
379
+ $input = $('<input type="hidden" name="key" value="value-from-attribute">').appendTo($form)
380
+ $input[0].value = 'value-from-script'
381
+
382
+ params = up.Params.fromForm($form)
383
+ expect(params.toArray()).toEqual [
384
+ { name: 'key', value: 'value-from-script' }
385
+ ]
386
+
387
+ it 'seralizes a <select> with its default selected option', ->
388
+ $form = $fixture('form')
389
+ $select = $('<select name="key"></select>').appendTo($form)
390
+ $option1 = $('<option value="value1">').appendTo($select)
391
+ $option2 = $('<option value="value2" selected>').appendTo($select)
392
+ $option3 = $('<option value="value3">').appendTo($select)
393
+
394
+ params = up.Params.fromForm($form)
395
+ expect(params.toArray()).toEqual [
396
+ { name: 'key', value: 'value2' }
397
+ ]
398
+
399
+ it 'seralizes a <select> that had its selection changed by a script', ->
400
+ $form = $fixture('form')
401
+ $select = $('<select name="key"></select>').appendTo($form)
402
+ $option1 = $('<option value="value1">').appendTo($select)
403
+ $option2 = $('<option value="value2" selected>').appendTo($select)
404
+ $option3 = $('<option value="value3">').appendTo($select)
405
+
406
+ $option2[0].selected = false
407
+ $option3[0].selected = true
408
+
409
+ params = up.Params.fromForm($form)
410
+ expect(params.toArray()).toEqual [
411
+ { name: 'key', value: 'value3' }
412
+ ]
413
+
414
+ it 'serializes a <select multiple> with multiple selected options into multiple params', ->
415
+ $form = $fixture('form')
416
+ $select = $('<select name="key" multiple></select>').appendTo($form)
417
+ $option1 = $('<option value="value1">').appendTo($select)
418
+ $option2 = $('<option value="value2" selected>').appendTo($select)
419
+ $option3 = $('<option value="value3" selected>').appendTo($select)
420
+
421
+ params = up.Params.fromForm($form)
422
+ expect(params.toArray()).toEqual [
423
+ { name: 'key', value: 'value2' },
424
+ { name: 'key', value: 'value3' }
425
+ ]
426
+
427
+ it 'serializes an <input type="file">'
428
+
429
+ it 'serializes an <input type="file" multiple> into multiple params'
430
+
431
+ it 'includes an <input type="checkbox"> that was [checked] by default', ->
432
+ $form = $fixture('form')
433
+ $input = $('<input type="checkbox" name="key" value="value" checked>').appendTo($form)
434
+
435
+ params = up.Params.fromForm($form)
436
+ expect(params.toArray()).toEqual [
437
+ { name: 'key', value: 'value' }
438
+ ]
439
+
440
+ it 'includes an <input type="checkbox"> that was checked by a script', ->
441
+ $form = $fixture('form')
442
+ $input = $('<input type="checkbox" name="key" value="value">').appendTo($form)
443
+ $input[0].checked = true
444
+
445
+ params = up.Params.fromForm($form)
446
+ expect(params.toArray()).toEqual [
447
+ { name: 'key', value: 'value' }
448
+ ]
449
+
450
+ it 'excludes an <input type="checkbox"> that is unchecked', ->
451
+ $form = $fixture('form')
452
+ $input = $('<input type="checkbox" name="key" value="value">').appendTo($form)
453
+ params = up.Params.fromForm($form)
454
+ expect(params.toArray()).toEqual []
455
+
456
+ it 'includes a checked <input type="radio"> in a radio button group that was [checked] by default', ->
457
+ $form = $fixture('form')
458
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
459
+ $button2 = $('<input type="radio" name="key" value="value2" checked>').appendTo($form)
460
+ $button3 = $('<input type="radio" name="key" value="value3">').appendTo($form)
461
+
462
+ params = up.Params.fromForm($form)
463
+ expect(params.toArray()).toEqual [
464
+ { name: 'key', value: 'value2' }
465
+ ]
466
+
467
+ it 'includes a checked <input type="radio"> in a radio button group that was checked by a script', ->
468
+ $form = $fixture('form')
469
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
470
+ $button2 = $('<input type="radio" name="key" value="value2" checked>').appendTo($form)
471
+ $button3 = $('<input type="radio" name="key" value="value3">').appendTo($form)
472
+
473
+ $button2[0].checked = false
474
+ $button3[0].checked = true
475
+
476
+ params = up.Params.fromForm($form)
477
+ expect(params.toArray()).toEqual [
478
+ { name: 'key', value: 'value3' }
479
+ ]
480
+
481
+ it 'excludes an radio button group if no button is selected', ->
482
+ $form = $fixture('form')
483
+ $button1 = $('<input type="radio" name="key" value="value1">').appendTo($form)
484
+ $button2 = $('<input type="radio" name="key" value="value2">').appendTo($form)
485
+
486
+ params = up.Params.fromForm($form)
487
+ expect(params.toArray()).toEqual []
488
+
489
+ it 'excludes an <input> that is [disabled] by default', ->
490
+ $form = $fixture('form')
491
+ $input = $('<input type="text" name="key" value="value" disabled>').appendTo($form)
492
+
493
+ params = up.Params.fromForm($form)
494
+ expect(params.toArray()).toEqual []
495
+
496
+ it 'excludes an <input> that was disabled by a script', ->
497
+ $form = $fixture('form')
498
+ $input = $('<input type="text" name="key" value="value">').appendTo($form)
499
+ $input[0].disabled = true
500
+
501
+ params = up.Params.fromForm($form)
502
+ expect(params.toArray()).toEqual []
503
+
504
+ it 'excludes an <input> without a [name] attribute', ->
505
+ $form = $fixture('form')
506
+ $input = $('<input type="text" value="value">').appendTo($form)
507
+
508
+ params = up.Params.fromForm($form)
509
+ expect(params.toArray()).toEqual []
510
+
511
+ it 'includes an <input readonly>', ->
512
+ $form = $fixture('form')
513
+ $input = $('<input type="text" name="key" value="value" readonly>').appendTo($form)
514
+
515
+ params = up.Params.fromForm($form)
516
+ expect(params.toArray()).toEqual [
517
+ { name: 'key', value: 'value' }
518
+ ]
519
+
520
+ it 'includes the focused submit button', ->
521
+ $form = $fixture('form')
522
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
523
+ $submit1 = $('<button type="submit" name="submit1-key" value="submit1-value">').appendTo($form)
524
+ $submit2 = $('<input type="submit" name="submit2-key" value="submit2-value">').appendTo($form)
525
+ $submit3 = $('<input type="submit" name="submit3-key" value="submit3-value">').appendTo($form)
526
+
527
+ $submit2.focus()
528
+
529
+ params = up.Params.fromForm($form)
530
+ expect(params.toArray()).toEqual [
531
+ { name: 'input-key', value: 'input-value' },
532
+ { name: 'submit2-key', value: 'submit2-value' }
533
+ ]
534
+
535
+ it 'includes a the first submit button if no button is focused', ->
536
+ $form = $fixture('form')
537
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
538
+ $submit1 = $('<button type="submit" name="submit1-key" value="submit1-value">').appendTo($form)
539
+ $submit2 = $('<input type="submit" name="submit2-key" value="submit2-value">').appendTo($form)
540
+
541
+ params = up.Params.fromForm($form)
542
+ expect(params.toArray()).toEqual [
543
+ { name: 'input-key', value: 'input-value' },
544
+ { name: 'submit1-key', value: 'submit1-value' }
545
+ ]
546
+
547
+ it 'excludes a submit button without a [name] attribute', ->
548
+ $form = $fixture('form')
549
+ $input = $('<input type="text" name="input-key" value="input-value">').appendTo($form)
550
+ $submit = $('<button type="submit" value="submit-value">').appendTo($form)
551
+
552
+ params = up.Params.fromForm($form)
553
+ expect(params.toArray()).toEqual [
554
+ { name: 'input-key', value: 'input-value' }
555
+ ]
556
+
557
+