unpoly-rails 0.56.7 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/dist/unpoly.js +1569 -793
  4. data/dist/unpoly.min.js +4 -4
  5. data/lib/assets/javascripts/unpoly.coffee +2 -0
  6. data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
  7. data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
  8. data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
  10. data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
  12. data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
  14. data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
  15. data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
  16. data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
  17. data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
  18. data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
  19. data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
  20. data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
  21. data/lib/assets/javascripts/unpoly/history.coffee +2 -2
  22. data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
  23. data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
  24. data/lib/assets/javascripts/unpoly/log.coffee +6 -5
  25. data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
  26. data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
  27. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
  28. data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
  29. data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
  31. data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
  32. data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
  33. data/lib/unpoly/rails/version.rb +1 -1
  34. data/package.json +1 -1
  35. data/spec_app/Gemfile.lock +1 -1
  36. data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
  37. data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
  38. data/spec_app/app/controllers/pages_controller.rb +4 -0
  39. data/spec_app/app/views/form_test/basics/new.erb +34 -5
  40. data/spec_app/app/views/form_test/submission_result.erb +2 -2
  41. data/spec_app/app/views/form_test/uploads/new.erb +15 -2
  42. data/spec_app/app/views/hash_test/unpoly.erb +30 -0
  43. data/spec_app/app/views/pages/start.erb +2 -1
  44. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
  45. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
  46. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
  47. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
  48. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
  49. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
  50. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
  51. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
  52. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
  53. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
  54. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
  55. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
  56. data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
  57. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
  58. data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
  59. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
  60. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
  61. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
  62. data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
  63. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
  64. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
  65. data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
  66. metadata +17 -5
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
@@ -7,19 +7,28 @@ describe 'up.proxy', ->
7
7
  describe 'up.request', ->
8
8
 
9
9
  it 'makes a request with the given URL and params', ->
10
- up.request('/foo', data: { key: 'value' }, method: 'post')
10
+ up.request('/foo', params: { key: 'value' }, method: 'post')
11
11
  request = @lastRequest()
12
12
  expect(request.url).toMatchUrl('/foo')
13
13
  expect(request.data()).toEqual(key: ['value'])
14
14
  expect(request.method).toEqual('POST')
15
15
 
16
16
  it 'also allows to pass the URL as a { url } option instead', ->
17
- up.request(url: '/foo', data: { key: 'value' }, method: 'post')
17
+ up.request(url: '/foo', params: { key: 'value' }, method: 'post')
18
18
  request = @lastRequest()
19
19
  expect(request.url).toMatchUrl('/foo')
20
20
  expect(request.data()).toEqual(key: ['value'])
21
21
  expect(request.method).toEqual('POST')
22
22
 
23
+ it 'allows to pass in an up.Request instance instead of an options object', ->
24
+ requestArg = new up.Request(url: '/foo', params: { key: 'value' }, method: 'post')
25
+ up.request(requestArg)
26
+
27
+ jasmineRequest = @lastRequest()
28
+ expect(jasmineRequest.url).toMatchUrl('/foo')
29
+ expect(jasmineRequest.data()).toEqual(key: ['value'])
30
+ expect(jasmineRequest.method).toEqual('POST')
31
+
23
32
  it 'submits the replacement targets as HTTP headers, so the server may choose to only frender the requested fragments', asyncSpec (next) ->
24
33
  up.request(url: '/foo', target: '.target', failTarget: '.fail-target')
25
34
 
@@ -31,7 +40,7 @@ describe 'up.proxy', ->
31
40
  it 'resolves to a Response object that contains information about the response and request', (done) ->
32
41
  promise = up.request(
33
42
  url: '/url'
34
- data: { key: 'value' }
43
+ params: { key: 'value' }
35
44
  method: 'post'
36
45
  target: '.target'
37
46
  )
@@ -44,7 +53,7 @@ describe 'up.proxy', ->
44
53
 
45
54
  promise.then (response) ->
46
55
  expect(response.request.url).toMatchUrl('/url')
47
- expect(response.request.data).toEqual(key: 'value')
56
+ expect(response.request.params).toEqual(key: 'value')
48
57
  expect(response.request.method).toEqual('POST')
49
58
  expect(response.request.target).toEqual('.target')
50
59
  expect(response.request.hash).toBeBlank()
@@ -57,6 +66,22 @@ describe 'up.proxy', ->
57
66
 
58
67
  done()
59
68
 
69
+ it 'resolves to a Response that contains the response headers', (done) ->
70
+ promise = up.request(url: '/url')
71
+
72
+ u.nextFrame =>
73
+ @respondWith
74
+ responseHeaders: { 'foo': 'bar', 'baz': 'bam' }
75
+ responseText: 'hello'
76
+
77
+ promise.then (response) ->
78
+ expect(response.getHeader('foo')).toEqual('bar')
79
+
80
+ # Lookup is case-insensitive
81
+ expect(response.getHeader('BAZ')).toEqual('bam')
82
+
83
+ done()
84
+
60
85
  it "preserves the URL hash in a separate { hash } property, since although it isn't sent to server, code might need it to process the response", (done) ->
61
86
  promise = up.request('/url#hash')
62
87
 
@@ -77,7 +102,7 @@ describe 'up.proxy', ->
77
102
  it 'updates the { method } property in the response object', (done) ->
78
103
  promise = up.request(
79
104
  url: '/url'
80
- data: { key: 'value' }
105
+ params: { key: 'value' }
81
106
  method: 'post'
82
107
  target: '.target'
83
108
  )
@@ -164,8 +189,8 @@ describe 'up.proxy', ->
164
189
  # See that an additional request was made
165
190
  expect(jasmine.Ajax.requests.count()).toEqual(2)
166
191
 
167
- it "does not explode if the original request's { data } is a FormData object", asyncSpec (next) ->
168
- up.request('/foo', method: 'post', data: new FormData()) # POST requests are not cached
192
+ it "does not explode if the original request's { params } is a FormData object", asyncSpec (next) ->
193
+ up.request('/foo', method: 'post', params: new FormData()) # POST requests are not cached
169
194
 
170
195
  next =>
171
196
  expect(jasmine.Ajax.requests.count()).toEqual(1)
@@ -286,11 +311,11 @@ describe 'up.proxy', ->
286
311
  headers = @lastRequest().requestHeaders
287
312
  expect(headers['X-Requested-With']).toEqual('Love')
288
313
 
289
- describe 'with { data } option', ->
314
+ describe 'with { params } option', ->
290
315
 
291
316
  it "uses the given params as a non-GET request's payload", asyncSpec (next) ->
292
317
  givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
293
- up.request(url: '/path', method: 'put', data: givenParams)
318
+ up.request(url: '/path', method: 'put', params: givenParams)
294
319
 
295
320
  next =>
296
321
  expect(@lastRequest().data()['foo-key']).toEqual(['foo-value'])
@@ -298,7 +323,7 @@ describe 'up.proxy', ->
298
323
 
299
324
  it "encodes the given params into the URL of a GET request", (done) ->
300
325
  givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
301
- promise = up.request(url: '/path', method: 'get', data: givenParams)
326
+ promise = up.request(url: '/path', method: 'get', params: givenParams)
302
327
 
303
328
  u.nextFrame =>
304
329
  expect(@lastRequest().url).toMatchUrl('/path?foo-key=foo-value&bar-key=bar-value')
@@ -310,7 +335,7 @@ describe 'up.proxy', ->
310
335
  # See that the response object has been updated by moving the data options
311
336
  # to the URL. This is important for up.dom code that works on response.request.
312
337
  expect(response.request.url).toMatchUrl('/path?foo-key=foo-value&bar-key=bar-value')
313
- expect(response.request.data).toBeBlank()
338
+ expect(response.request.params).toBeBlank()
314
339
  done()
315
340
 
316
341
  it 'caches server responses for the configured duration', asyncSpec (next) ->
@@ -384,8 +409,8 @@ describe 'up.proxy', ->
384
409
  next => expect(jasmine.Ajax.requests.count()).toEqual(2)
385
410
 
386
411
  it "doesn't reuse responses when asked for the same path, but different params", asyncSpec (next) ->
387
- next => up.request(url: '/path', data: { query: 'foo' })
388
- next => up.request(url: '/path', data: { query: 'bar' })
412
+ next => up.request(url: '/path', params: { query: 'foo' })
413
+ next => up.request(url: '/path', params: { query: 'bar' })
389
414
  next => expect(jasmine.Ajax.requests.count()).toEqual(2)
390
415
 
391
416
  it "reuses a response for an 'html' selector when asked for the same path and any other selector", asyncSpec (next) ->
@@ -460,6 +485,7 @@ describe 'up.proxy', ->
460
485
  request = @lastRequest()
461
486
  expect(request.method).toEqual('POST')
462
487
  expect(request.data()['_method']).toEqual([method])
488
+ # expect(request.data()['foo']).toEqual('bar')
463
489
 
464
490
  describe 'with config.maxRequests set', ->
465
491
 
@@ -656,7 +682,7 @@ describe 'up.proxy', ->
656
682
 
657
683
  it 'does not emit up:proxy:recover if a delayed up:proxy:slow was never emitted due to a fast response', asyncSpec (next) ->
658
684
  next =>
659
- up.proxy.config.slowDelay = 100
685
+ up.proxy.config.slowDelay = 200
660
686
  up.request(url: '/foo')
661
687
 
662
688
  next =>
@@ -664,13 +690,13 @@ describe 'up.proxy', ->
664
690
  'up:proxy:load'
665
691
  ])
666
692
 
667
- next.after 50, =>
693
+ next.after 100, =>
668
694
  jasmine.Ajax.requests.at(0).respondWith
669
695
  status: 200
670
696
  contentType: 'text/html'
671
697
  responseText: 'foo'
672
698
 
673
- next.after 150, =>
699
+ next.after 250, =>
674
700
  expect(@events).toEqual([
675
701
  'up:proxy:load',
676
702
  'up:proxy:loaded'
@@ -756,11 +782,24 @@ describe 'up.proxy', ->
756
782
  cachedPromise = up.proxy.get(url: '/path', target: '.target')
757
783
  expect(u.isPromise(cachedPromise)).toBe(true)
758
784
 
759
- it "does not load a link whose method has side-effects", asyncSpec (next) ->
760
- $link = affix('a[href="/path"][data-method="post"]')
761
- up.proxy.preload($link)
785
+ it "does not load a link whose method has side-effects", (done) ->
786
+ affix('.target')
787
+ $link = affix('a[href="/path"][up-target=".target"][data-method="post"]')
788
+ preloadPromise = up.proxy.preload($link)
789
+
790
+ promiseState(preloadPromise).then (result) ->
791
+ expect(result.state).toEqual('rejected')
792
+ expect(up.proxy.get(url: '/path', target: '.target')).toBeUndefined()
793
+ done()
794
+
795
+ it 'accepts options', asyncSpec (next) ->
796
+ affix('.target')
797
+ $link = affix('a[href="/path"][up-target=".target"]')
798
+ up.proxy.preload($link, url: '/options-path')
762
799
 
763
- next => expect(up.proxy.get(url: '/path')).toBeUndefined()
800
+ next =>
801
+ cachedPromise = up.proxy.get(url: '/options-path', target: '.target')
802
+ expect(u.isPromise(cachedPromise)).toBe(true)
764
803
 
765
804
  describe 'for an [up-target] link', ->
766
805
 
@@ -784,7 +823,7 @@ describe 'up.proxy', ->
784
823
  up.proxy.preload($link)
785
824
 
786
825
  next =>
787
- expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(url: '/path', preload: true))
826
+ expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(preload: true))
788
827
 
789
828
  describe 'for an [up-modal] link', ->
790
829
 
@@ -851,7 +890,7 @@ describe 'up.proxy', ->
851
890
  up.proxy.preload($link)
852
891
 
853
892
  next =>
854
- expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(url: '/path', preload: true))
893
+ expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(preload: true))
855
894
 
856
895
  describe 'for an [up-popup] link', ->
857
896
 
@@ -921,7 +960,7 @@ describe 'up.proxy', ->
921
960
  up.proxy.preload($link)
922
961
 
923
962
  next =>
924
- expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(url: '/path', preload: true))
963
+ expect(requestSpy).toHaveBeenCalledWith(jasmine.objectContaining(preload: true))
925
964
 
926
965
  describeFallback 'canPushState', ->
927
966
 
@@ -935,16 +974,16 @@ describe 'up.proxy', ->
935
974
  describe 'up.proxy.get', ->
936
975
 
937
976
  it 'returns an existing cache entry for the given request', ->
938
- promise1 = up.request(url: '/foo', data: { key: 'value' })
939
- promise2 = up.proxy.get(url: '/foo', data: { key: 'value' })
977
+ promise1 = up.request(url: '/foo', params: { key: 'value' })
978
+ promise2 = up.proxy.get(url: '/foo', params: { key: 'value' })
940
979
  expect(promise1).toBe(promise2)
941
980
 
942
981
  it 'returns undefined if the given request is not cached', ->
943
- promise = up.proxy.get(url: '/foo', data: { key: 'value' })
982
+ promise = up.proxy.get(url: '/foo', params: { key: 'value' })
944
983
  expect(promise).toBeUndefined()
945
984
 
946
- it "returns undefined if the given request's { data } is a FormData object", ->
947
- promise = up.proxy.get(url: '/foo', data: new FormData())
985
+ it "returns undefined if the given request's { params } is a FormData object", ->
986
+ promise = up.proxy.get(url: '/foo', params: new FormData())
948
987
  expect(promise).toBeUndefined()
949
988
 
950
989
  describe 'up.proxy.set', ->
@@ -962,7 +1001,7 @@ describe 'up.proxy', ->
962
1001
  it 'does nothing if the given request is not cached'
963
1002
 
964
1003
  it 'does not crash when passed a request with FormData (bugfix)', ->
965
- removal = -> up.proxy.remove(url: '/path', data: new FormData())
1004
+ removal = -> up.proxy.remove(url: '/path', params: new FormData())
966
1005
  expect(removal).not.toThrowError()
967
1006
 
968
1007
  describe 'up.proxy.clear', ->
@@ -1041,7 +1080,7 @@ describe 'up.proxy', ->
1041
1080
 
1042
1081
  Trigger.hoverSequence($link)
1043
1082
 
1044
- next.after 2, =>
1083
+ next.after 50, =>
1045
1084
  expect(jasmine.Ajax.requests.count()).toEqual(1)
1046
1085
 
1047
1086
  @respondWith
@@ -1067,7 +1106,7 @@ describe 'up.proxy', ->
1067
1106
  expect(window).not.toHaveUnhandledRejections() if REJECTION_EVENTS_SUPPORTED
1068
1107
 
1069
1108
  it 'triggers a separate AJAX request when hovered multiple times and the cache expires between hovers', asyncSpec (next) ->
1070
- up.proxy.config.cacheExpiry = 50
1109
+ up.proxy.config.cacheExpiry = 100
1071
1110
  up.proxy.config.preloadDelay = 0
1072
1111
 
1073
1112
  $element = affix('a[href="/foo"][up-preload]')
@@ -1075,17 +1114,17 @@ describe 'up.proxy', ->
1075
1114
 
1076
1115
  Trigger.hoverSequence($element)
1077
1116
 
1078
- next.after 1, =>
1117
+ next.after 10, =>
1079
1118
  expect(jasmine.Ajax.requests.count()).toEqual(1)
1080
1119
 
1081
- next.after 1, =>
1120
+ next.after 10, =>
1082
1121
  Trigger.hoverSequence($element)
1083
1122
 
1084
- next.after 1, =>
1123
+ next.after 10, =>
1085
1124
  expect(jasmine.Ajax.requests.count()).toEqual(1)
1086
1125
 
1087
- next.after 60, =>
1126
+ next.after 150, =>
1088
1127
  Trigger.hoverSequence($element)
1089
1128
 
1090
- next.after 1, =>
1129
+ next.after 30, =>
1091
1130
  expect(jasmine.Ajax.requests.count()).toEqual(2)
@@ -1,4 +1,6 @@
1
1
  describe 'up.syntax', ->
2
+
3
+ u = up.util
2
4
 
3
5
  describe 'JavaScript functions', ->
4
6
 
@@ -96,28 +98,38 @@ describe 'up.syntax', ->
96
98
  promiseState(promise).then (result) ->
97
99
  expect(result.state).toEqual('fulfilled')
98
100
 
99
- it 'parses an up-data attribute as JSON and passes the parsed object as a second argument to the initializer', ->
101
+ describe 'passing of [up-data]', ->
102
+
103
+ it 'parses an [up-data] attribute as JSON and passes the parsed object as a second argument to the compiler', ->
104
+ observeArgs = jasmine.createSpy()
105
+ up.compiler '.child', ($element, data) ->
106
+ observeArgs($element.attr('class'), data)
107
+
108
+ data = { key1: 'value1', key2: 'value2' }
109
+
110
+ $tag = affix(".child").attr('up-data', JSON.stringify(data))
111
+ up.hello($tag)
100
112
 
101
- observeArgs = jasmine.createSpy()
102
- up.compiler '.child', ($element, data) ->
103
- observeArgs($element.attr('class'), data)
113
+ expect(observeArgs).toHaveBeenCalledWith('child', data)
104
114
 
105
- data = { key1: 'value1', key2: 'value2' }
115
+ it 'passes an empty object as a second argument to the compiler if there is no [up-data] attribute', ->
116
+ observeArgs = jasmine.createSpy()
117
+ up.compiler '.child', ($element, data) ->
118
+ observeArgs($element.attr('class'), data)
106
119
 
107
- $tag = affix(".child").attr('up-data', JSON.stringify(data))
108
- up.hello($tag)
120
+ up.hello(affix(".child"))
109
121
 
110
- expect(observeArgs).toHaveBeenCalledWith('child', data)
122
+ expect(observeArgs).toHaveBeenCalledWith('child', {})
111
123
 
112
- it 'passes an empty object as a second argument to the initializer if there is no up-data attribute', ->
124
+ it 'does not parse an [up-data] attribute if the compiler function only takes a single argument', ->
125
+ parseDataSpy = spyOn(up.syntax, 'data').and.returnValue({})
113
126
 
114
- observeArgs = jasmine.createSpy()
115
- up.compiler '.child', ($element, data) ->
116
- observeArgs($element.attr('class'), data)
127
+ $child = affix(".child")
117
128
 
118
- up.hello(affix(".child"))
129
+ up.compiler '.child', ($element) -> # no-op
130
+ up.hello($child)
119
131
 
120
- expect(observeArgs).toHaveBeenCalledWith('child', {})
132
+ expect(parseDataSpy).not.toHaveBeenCalled()
121
133
 
122
134
  it 'compiles matching elements one-by-one', ->
123
135
  compiler = jasmine.createSpy('compiler')
@@ -143,6 +155,13 @@ describe 'up.syntax', ->
143
155
  expect(compiler.calls.count()).toEqual(1)
144
156
  expect(compiler).toHaveBeenCalledWith($both)
145
157
 
158
+ it 'throws an error if the batch compiler returns a destructor', ->
159
+ destructor = ->
160
+ up.compiler '.element', { batch: true }, ($element) -> destructor
161
+ $container = affix('.element')
162
+ compile = -> up.hello($container)
163
+ expect(compile).toThrowError(/cannot return destructor/i)
164
+
146
165
  describe 'with { keep } option', ->
147
166
 
148
167
  it 'adds an up-keep attribute to the fragment during compilation', ->
@@ -238,4 +257,18 @@ describe 'up.syntax', ->
238
257
 
239
258
  it 'should have tests'
240
259
 
241
-
260
+ describe 'up.syntax.data', ->
261
+
262
+ it 'returns the [up-data] attribute of the given element, parsed as JSON', ->
263
+ $element = affix('.element').attr('up-data', '{ "foo": 1, "bar": 2 }')
264
+ data = up.syntax.data($element)
265
+ expect(data).toEqual(foo: 1, bar: 2)
266
+
267
+ it 'returns en empty object if the given element has no [up-data] attribute', ->
268
+ $element = affix('.element')
269
+ data = up.syntax.data($element)
270
+ expect(data).toEqual({})
271
+
272
+ it 'returns undefined if undefined is passed instead of an element', ->
273
+ data = up.syntax.data(undefined)
274
+ expect(data).toBeUndefined()
@@ -4,6 +4,13 @@ describe 'up.util', ->
4
4
 
5
5
  describe 'JavaScript functions', ->
6
6
 
7
+ # describe 'up.util.flatMap', ->
8
+ #
9
+ # it 'collects the Array results of the given map function, then concatenates the result arrays into one flat array', ->
10
+ # fun = (x) -> [x, x]
11
+ # result = up.util.flatMap([1, 2, 3], fun)
12
+ # expect(result).toEqual([1, 1, 2, 2, 3, 3])
13
+
7
14
  describe 'up.util.uniq', ->
8
15
 
9
16
  it 'returns the given array with duplicates elements removed', ->
@@ -35,6 +42,56 @@ describe 'up.util', ->
35
42
  result = up.util.uniqBy(input, 'length')
36
43
  expect(result).toEqual ['foo', 'apple', 'orange']
37
44
 
45
+ # describe 'up.util.parsePath', ->
46
+ #
47
+ # it 'parses a plain name', ->
48
+ # path = up.util.parsePath("foo")
49
+ # expect(path).toEqual ['foo']
50
+ #
51
+ # it 'considers underscores to be part of a name', ->
52
+ # path = up.util.parsePath("foo_bar")
53
+ # expect(path).toEqual ['foo_bar']
54
+ #
55
+ # it 'considers dashes to be part of a name', ->
56
+ # path = up.util.parsePath("foo-bar")
57
+ # expect(path).toEqual ['foo-bar']
58
+ #
59
+ # it 'parses dot-separated names into multiple path segments', ->
60
+ # path = up.util.parsePath('foo.bar.baz')
61
+ # expect(path).toEqual ['foo', 'bar', 'baz']
62
+ #
63
+ # it 'parses nested params notation with square brackets', ->
64
+ # path = up.util.parsePath('user[account][email]')
65
+ # expect(path).toEqual ['user', 'account', 'email']
66
+ #
67
+ # it 'parses double quotes in square brackets', ->
68
+ # path = up.util.parsePath('user["account"]["email"]')
69
+ # expect(path).toEqual ['user', 'account', 'email']
70
+ #
71
+ # it 'parses single quotes in square brackets', ->
72
+ # path = up.util.parsePath("user['account']['email']")
73
+ # expect(path).toEqual ['user', 'account', 'email']
74
+ #
75
+ # it 'allows square brackets inside quotes', ->
76
+ # path = up.util.parsePath("element['a[up-href]']")
77
+ # expect(path).toEqual ['element', 'a[up-href]']
78
+ #
79
+ # it 'allows single quotes inside double quotes', ->
80
+ # path = up.util.parsePath("element[\"a[up-href='foo']\"]")
81
+ # expect(path).toEqual ['element', "a[up-href='foo']"]
82
+ #
83
+ # it 'allows double quotes inside single quotes', ->
84
+ # path = up.util.parsePath("element['a[up-href=\"foo\"]']")
85
+ # expect(path).toEqual ['element', 'a[up-href="foo"]']
86
+ #
87
+ # it 'allows dots in square brackets when it is quoted', ->
88
+ # path = up.util.parsePath('elements[".foo"]')
89
+ # expect(path).toEqual ['elements', '.foo']
90
+ #
91
+ # it 'allows different notation for each segment', ->
92
+ # path = up.util.parsePath('foo.bar[baz]["bam"][\'qux\']')
93
+ # expect(path).toEqual ['foo', 'bar', 'baz', 'bam', 'qux']
94
+
38
95
  describe 'up.util.map', ->
39
96
 
40
97
  it 'creates a new array of values by calling the given function on each item of the given array', ->
@@ -211,6 +268,17 @@ describe 'up.util', ->
211
268
  fooBar: 'one'
212
269
  barBaz: 'two'
213
270
 
271
+ # describe 'up.util.lowerCaseKeys', ->
272
+ #
273
+ # it "returns a copy of the given object will all keys in lower case", ->
274
+ # input =
275
+ # 'A-B': 'C-D'
276
+ # 'E-F': 'G-H'
277
+ # result = up.util.lowerCaseKeys(input)
278
+ # expect(result).toEqual
279
+ # 'a-b': 'C-D'
280
+ # 'e-f': 'G-H'
281
+
214
282
  describe 'up.util.DivertibleChain', ->
215
283
 
216
284
  it "instantiates a task queue whose (2..n)th tasks can be changed by calling '.asap'", (done) ->
@@ -569,7 +637,11 @@ describe 'up.util', ->
569
637
  expect(up.util.selectorForElement($element)).toBe('input[name="name-value"]')
570
638
 
571
639
  it "prefers using the element's classes to using the element's ARIA label", ->
572
- $element = affix('div.class1.class2')
640
+ $element = affix('div.class1.class2[aria-label="ARIA label value"]')
641
+ expect(up.util.selectorForElement($element)).toBe(".class1.class2")
642
+
643
+ it 'does not use Unpoly classes to compose a class selector', ->
644
+ $element = affix('div.class1.up-current.class2')
573
645
  expect(up.util.selectorForElement($element)).toBe(".class1.class2")
574
646
 
575
647
  it "prefers using the element's ARIA label to using the element's tag name", ->
@@ -862,56 +934,6 @@ describe 'up.util', ->
862
934
  up.util.remove(array, obj2)
863
935
  expect(array).toEqual [obj1, obj3]
864
936
 
865
-
866
- describe 'up.util.requestDataAsQuery', ->
867
-
868
- encodedOpeningBracket = '%5B'
869
- encodedClosingBracket = '%5D'
870
- encodedSpace = '%20'
871
-
872
- it 'returns the query section for the given object', ->
873
- string = up.util.requestDataAsQuery('foo-key': 'foo value', 'bar-key': 'bar value')
874
- expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
875
-
876
- it 'returns the query section for the given nested object', ->
877
- string = up.util.requestDataAsQuery('foo-key': { 'bar-key': 'bar-value' }, 'bam-key': 'bam-value')
878
- expect(string).toEqual("foo-key#{encodedOpeningBracket}bar-key#{encodedClosingBracket}=bar-value&bam-key=bam-value")
879
-
880
- it 'returns the query section for the given array with { name } and { value } keys', ->
881
- string = up.util.requestDataAsQuery([
882
- { name: 'foo-key', value: 'foo value' },
883
- { name: 'bar-key', value: 'bar value' }
884
- ])
885
- expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
886
-
887
- it 'returns a given query string', ->
888
- string = up.util.requestDataAsQuery('foo=bar')
889
- expect(string).toEqual('foo=bar')
890
-
891
- it 'strips a leading question mark from the given query string', ->
892
- string = up.util.requestDataAsQuery('?foo=bar')
893
- expect(string).toEqual('foo=bar')
894
-
895
- it 'returns an empty string for an empty object', ->
896
- string = up.util.requestDataAsQuery({})
897
- expect(string).toEqual('')
898
-
899
- it 'returns an empty string for an empty string', ->
900
- string = up.util.requestDataAsQuery('')
901
- expect(string).toEqual('')
902
-
903
- it 'returns an empty string for undefined', ->
904
- string = up.util.requestDataAsQuery(undefined)
905
- expect(string).toEqual('')
906
-
907
- it 'URL-encodes characters in the key and value', ->
908
- string = up.util.requestDataAsQuery({ 'äpfel': 'bäume' })
909
- expect(string).toEqual('%C3%A4pfel=b%C3%A4ume')
910
-
911
- it 'URL-encodes plus characters', ->
912
- string = up.util.requestDataAsQuery({ 'my+key': 'my+value' })
913
- expect(string).toEqual('my%2Bkey=my%2Bvalue')
914
-
915
937
  describe 'up.util.unresolvablePromise', ->
916
938
 
917
939
  it 'return a pending promise', (done) ->
@@ -925,84 +947,6 @@ describe 'up.util', ->
925
947
  two = up.util.unresolvablePromise()
926
948
  expect(one).not.toBe(two)
927
949
 
928
- describe 'up.util.requestDataAsArray', ->
929
-
930
- it 'normalized null to an empty array', ->
931
- array = up.util.requestDataAsArray(null)
932
- expect(array).toEqual([])
933
-
934
- it 'normalized undefined to an empty array', ->
935
- array = up.util.requestDataAsArray(undefined)
936
- expect(array).toEqual([])
937
-
938
- it 'normalizes an object hash to an array of objects with { name } and { value } keys', ->
939
- array = up.util.requestDataAsArray(
940
- 'foo-key': 'foo-value'
941
- 'bar-key': 'bar-value'
942
- )
943
- expect(array).toEqual([
944
- { name: 'foo-key', value: 'foo-value' },
945
- { name: 'bar-key', value: 'bar-value' },
946
- ])
947
-
948
- it 'normalizes a nested object hash to a flat array using param naming conventions', ->
949
- array = up.util.requestDataAsArray(
950
- 'foo-key': 'foo-value'
951
- 'bar-key': {
952
- 'bam-key': 'bam-value'
953
- 'baz-key': {
954
- 'qux-key': 'qux-value'
955
- }
956
- }
957
- )
958
- expect(array).toEqual([
959
- { name: 'foo-key', value: 'foo-value' },
960
- { name: 'bar-key[bam-key]', value: 'bam-value' },
961
- { name: 'bar-key[baz-key][qux-key]', value: 'qux-value' },
962
- ])
963
-
964
- it 'returns a given array without modification', ->
965
- array = up.util.requestDataAsArray([
966
- { name: 'foo-key', value: 'foo-value' },
967
- { name: 'bar-key', value: 'bar-value' },
968
- ])
969
- expect(array).toEqual([
970
- { name: 'foo-key', value: 'foo-value' },
971
- { name: 'bar-key', value: 'bar-value' },
972
- ])
973
-
974
- it 'does not URL-encode special characters keys or values', ->
975
- array = up.util.requestDataAsArray(
976
- 'äpfel': { 'bäume': 'börse' }
977
- )
978
- expect(array).toEqual([
979
- { name: 'äpfel[bäume]', value: 'börse' },
980
- ])
981
-
982
- it 'does not URL-encode spaces in keys or values', ->
983
- array = up.util.requestDataAsArray(
984
- 'my key': 'my value'
985
- )
986
- expect(array).toEqual([
987
- { name: 'my key', value: 'my value' },
988
- ])
989
-
990
- it 'does not URL-encode ampersands in keys or values', ->
991
- array = up.util.requestDataAsArray(
992
- 'my&key': 'my&value'
993
- )
994
- expect(array).toEqual([
995
- { name: 'my&key', value: 'my&value' },
996
- ])
997
-
998
- it 'does not URL-encode equal signs in keys or values', ->
999
- array = up.util.requestDataAsArray(
1000
- 'my=key': 'my=value'
1001
- )
1002
- expect(array).toEqual([
1003
- { name: 'my=key', value: 'my=value' },
1004
- ])
1005
-
1006
950
  describe 'up.util.flatten', ->
1007
951
 
1008
952
  it 'flattens the given array', ->
@@ -1105,6 +1049,9 @@ describe 'up.util', ->
1105
1049
  it 'returns true for an object literal', ->
1106
1050
  expect(up.util.isOptions({ foo: 'bar'})).toBe(true)
1107
1051
 
1052
+ it 'returns true for a prototype-less object', ->
1053
+ expect(up.util.isOptions(Object.create(null))).toBe(true)
1054
+
1108
1055
  it 'returns false for undefined', ->
1109
1056
  expect(up.util.isOptions(undefined)).toBe(false)
1110
1057
 
@@ -1116,18 +1063,24 @@ describe 'up.util', ->
1116
1063
  fn.key = 'value'
1117
1064
  expect(up.util.isOptions(fn)).toBe(false)
1118
1065
 
1119
- it 'returns false for an array', ->
1066
+ it 'returns false for an Array', ->
1120
1067
  expect(up.util.isOptions(['foo'])).toBe(false)
1121
1068
 
1122
1069
  it 'returns false for a jQuery collection', ->
1123
1070
  expect(up.util.isOptions($('body'))).toBe(false)
1124
1071
 
1125
- it 'returns false for a promise', ->
1072
+ it 'returns false for a Promise', ->
1126
1073
  expect(up.util.isOptions(Promise.resolve())).toBe(false)
1127
1074
 
1128
1075
  it 'returns false for a FormData object', ->
1129
1076
  expect(up.util.isOptions(new FormData())).toBe(false)
1130
1077
 
1078
+ it 'returns false for a Date', ->
1079
+ expect(up.util.isOptions(new Date())).toBe(false)
1080
+
1081
+ it 'returns false for a RegExp', ->
1082
+ expect(up.util.isOptions(new RegExp('foo'))).toBe(false)
1083
+
1131
1084
  describe 'up.util.isObject', ->
1132
1085
 
1133
1086
  it 'returns true for an Object instance', ->
@@ -1159,6 +1112,70 @@ describe 'up.util', ->
1159
1112
  it 'returns true for a FormData object', ->
1160
1113
  expect(up.util.isObject(new FormData())).toBe(true)
1161
1114
 
1115
+ describe 'up.util.merge', ->
1116
+
1117
+ it 'merges the given objects', ->
1118
+ obj = { a: '1', b: '2' }
1119
+ other = { b: '3', c: '4' }
1120
+ obj = up.util.merge(obj, other)
1121
+ expect(obj).toEqual { a: '1', b: '3', c: '4' }
1122
+
1123
+ it 'overrides (not merges) keys with object value', ->
1124
+ obj = { a: '1', b: { c: '2', d: '3' } }
1125
+ other = { e: '4', b: { f: '5', g: '6' }}
1126
+ obj = up.util.merge(obj, other)
1127
+ expect(obj).toEqual { a: '1', e: '4', b: { f: '5', g: '6' } }
1128
+
1129
+ it 'ignores undefined arguments', ->
1130
+ obj = { a: 1, b: 2 }
1131
+
1132
+ result = up.util.merge(obj, undefined)
1133
+ expect(result).toEqual { a: 1, b: 2 }
1134
+
1135
+ reverseResult = up.util.merge(undefined, obj)
1136
+ expect(reverseResult).toEqual { a: 1, b: 2 }
1137
+
1138
+ it 'ignores null arguments', ->
1139
+ obj = { a: 1, b: 2 }
1140
+
1141
+ result = up.util.merge(obj, null)
1142
+ expect(result).toEqual { a: 1, b: 2 }
1143
+
1144
+ reverseResult = up.util.merge(null, obj)
1145
+ expect(reverseResult).toEqual { a: 1, b: 2 }
1146
+
1147
+ # describe 'up.util.deepMerge', ->
1148
+ #
1149
+ # it 'recursively merges the given objects', ->
1150
+ # obj = { a: '1', b: { c: '2', d: '3' } }
1151
+ # other = { e: '4', b: { f: '5', g: '6' }}
1152
+ # obj = up.util.deepMerge(obj, other)
1153
+ # expect(obj).toEqual { a: '1', e: '4', b: { c: '2', d: '3', f: '5', g: '6' } }
1154
+ #
1155
+ # it 'ignores undefined arguments', ->
1156
+ # obj = { a: 1, b: 2 }
1157
+ #
1158
+ # result = up.util.deepMerge(obj, undefined)
1159
+ # expect(result).toEqual { a: 1, b: 2 }
1160
+ #
1161
+ # reverseResult = up.util.deepMerge(undefined, obj)
1162
+ # expect(reverseResult).toEqual { a: 1, b: 2 }
1163
+ #
1164
+ # it 'ignores null arguments', ->
1165
+ # obj = { a: 1, b: 2 }
1166
+ #
1167
+ # result = up.util.deepMerge(obj, null)
1168
+ # expect(result).toEqual { a: 1, b: 2 }
1169
+ #
1170
+ # reverseResult = up.util.deepMerge(null, obj)
1171
+ # expect(reverseResult).toEqual { a: 1, b: 2 }
1172
+ #
1173
+ # it 'overwrites (and does not concatenate) array values', ->
1174
+ # obj = { a: ['1', '2'] }
1175
+ # other = { a: ['3', '4'] }
1176
+ # obj = up.util.deepMerge(obj, other)
1177
+ # expect(obj).toEqual { a: ['3', '4'] }
1178
+
1162
1179
  describe 'up.util.memoize', ->
1163
1180
 
1164
1181
  it 'returns a function that calls the memoized function', ->