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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -1
- data/dist/unpoly.js +1569 -793
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly.coffee +2 -0
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
- data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
- data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
- data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
- data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
- data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
- data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
- data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
- data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
- data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
- data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
- data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
- data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
- data/lib/assets/javascripts/unpoly/history.coffee +2 -2
- data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
- data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
- data/lib/assets/javascripts/unpoly/log.coffee +6 -5
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
- data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
- data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
- data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
- data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
- data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
- data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
- data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +1 -1
- data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
- data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
- data/spec_app/app/controllers/pages_controller.rb +4 -0
- data/spec_app/app/views/form_test/basics/new.erb +34 -5
- data/spec_app/app/views/form_test/submission_result.erb +2 -2
- data/spec_app/app/views/form_test/uploads/new.erb +15 -2
- data/spec_app/app/views/hash_test/unpoly.erb +30 -0
- data/spec_app/app/views/pages/start.erb +2 -1
- data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
- data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
- data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
- data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
- data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
- data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
- data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
- data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
- data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
- metadata +17 -5
- 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',
|
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',
|
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
|
-
|
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.
|
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
|
-
|
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 {
|
168
|
-
up.request('/foo', method: 'post',
|
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 {
|
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',
|
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',
|
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.
|
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',
|
388
|
-
next => up.request(url: '/path',
|
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 =
|
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
|
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
|
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",
|
760
|
-
|
761
|
-
up.
|
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 =>
|
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(
|
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(
|
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(
|
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',
|
939
|
-
promise2 = up.proxy.get(url: '/foo',
|
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',
|
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 {
|
947
|
-
promise = up.proxy.get(url: '/foo',
|
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',
|
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
|
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 =
|
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
|
1117
|
+
next.after 10, =>
|
1079
1118
|
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
1080
1119
|
|
1081
|
-
next.after
|
1120
|
+
next.after 10, =>
|
1082
1121
|
Trigger.hoverSequence($element)
|
1083
1122
|
|
1084
|
-
next.after
|
1123
|
+
next.after 10, =>
|
1085
1124
|
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
1086
1125
|
|
1087
|
-
next.after
|
1126
|
+
next.after 150, =>
|
1088
1127
|
Trigger.hoverSequence($element)
|
1089
1128
|
|
1090
|
-
next.after
|
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
|
-
|
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
|
-
|
102
|
-
up.compiler '.child', ($element, data) ->
|
103
|
-
observeArgs($element.attr('class'), data)
|
113
|
+
expect(observeArgs).toHaveBeenCalledWith('child', data)
|
104
114
|
|
105
|
-
|
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
|
-
|
108
|
-
up.hello($tag)
|
120
|
+
up.hello(affix(".child"))
|
109
121
|
|
110
|
-
|
122
|
+
expect(observeArgs).toHaveBeenCalledWith('child', {})
|
111
123
|
|
112
|
-
|
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
|
-
|
115
|
-
up.compiler '.child', ($element, data) ->
|
116
|
-
observeArgs($element.attr('class'), data)
|
127
|
+
$child = affix(".child")
|
117
128
|
|
118
|
-
|
129
|
+
up.compiler '.child', ($element) -> # no-op
|
130
|
+
up.hello($child)
|
119
131
|
|
120
|
-
|
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
|
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
|
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', ->
|