unpoly-rails 0.56.7 → 0.57.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.
- 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', ->
|