unpoly-rails 0.60.3 → 1.0.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/.ruby-version +1 -2
- data/CHANGELOG.md +84 -0
- data/README.md +1 -1
- data/Rakefile +11 -1
- data/dist/unpoly.js +226 -91
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +10 -5
- data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +21 -12
- data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +2 -2
- data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +11 -3
- data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +1 -0
- data/lib/assets/javascripts/unpoly/element.coffee.erb +9 -6
- data/lib/assets/javascripts/unpoly/event.coffee.erb +12 -4
- data/lib/assets/javascripts/unpoly/form.coffee.erb +85 -14
- data/lib/assets/javascripts/unpoly/fragment.coffee.erb +7 -3
- data/lib/assets/javascripts/unpoly/framework.coffee +7 -9
- data/lib/assets/javascripts/unpoly/link.coffee.erb +1 -1
- data/lib/assets/javascripts/unpoly/log.coffee +6 -2
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +4 -2
- data/lib/assets/javascripts/unpoly/motion.coffee.erb +1 -1
- data/lib/assets/javascripts/unpoly/protocol.coffee +1 -1
- data/lib/assets/javascripts/unpoly/syntax.coffee.erb +3 -4
- data/lib/assets/javascripts/unpoly/util.coffee.erb +4 -2
- data/lib/assets/javascripts/unpoly/viewport.coffee.erb +26 -2
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile +2 -4
- data/spec_app/Gemfile.lock +23 -27
- data/spec_app/app/views/compiler_test/timestamp.erb +1 -0
- data/spec_app/app/views/css_test/modal.erb +1 -1
- data/spec_app/app/views/css_test/popup.erb +1 -1
- data/spec_app/app/views/pages/start.erb +2 -1
- data/spec_app/app/views/replace_test/table.erb +16 -0
- data/spec_app/spec/javascripts/up/event_spec.js.coffee +34 -0
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +128 -0
- data/spec_app/spec/javascripts/up/fragment_spec.js.coffee +36 -1
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +7 -2
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -1
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +2 -1
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +28 -0
- metadata +4 -4
@@ -6,6 +6,47 @@ describe 'up.form', ->
|
|
6
6
|
|
7
7
|
describe 'JavaScript functions', ->
|
8
8
|
|
9
|
+
describe 'up.form.fields', ->
|
10
|
+
|
11
|
+
it 'returns a list of form fields within the given element', ->
|
12
|
+
form = fixture('form')
|
13
|
+
textField = e.affix(form, 'input[type=text]')
|
14
|
+
select = e.affix(form, 'select')
|
15
|
+
results = up.form.fields(form)
|
16
|
+
expect(results).toMatchList([textField, select])
|
17
|
+
|
18
|
+
it 'returns an empty list if the given element contains no form fields', ->
|
19
|
+
form = fixture('form')
|
20
|
+
results = up.form.fields(form)
|
21
|
+
expect(results).toMatchList([])
|
22
|
+
|
23
|
+
it 'returns a list of the given element if the element is itself a form field', ->
|
24
|
+
textArea = fixture('textarea')
|
25
|
+
results = up.form.fields(textArea)
|
26
|
+
expect(results).toMatchList([textArea])
|
27
|
+
|
28
|
+
it 'ignores fields outside the given form', ->
|
29
|
+
form1 = fixture('form')
|
30
|
+
form1Field = e.affix(form1, 'input[type=text]')
|
31
|
+
form2 = fixture('form')
|
32
|
+
form2Field = e.affix(form2, 'input[type=text]')
|
33
|
+
results = up.form.fields(form1)
|
34
|
+
expect(results).toMatchList([form1Field])
|
35
|
+
|
36
|
+
it "includes fields outside the form with a [form] attribute matching the given form's ID", ->
|
37
|
+
form = fixture('form#form-id')
|
38
|
+
insideField = e.affix(form, 'input[type=text]')
|
39
|
+
outsideField = fixture('input[type=text][form=form-id]')
|
40
|
+
results = up.form.fields(form)
|
41
|
+
expect(results).toMatchList([insideField, outsideField])
|
42
|
+
|
43
|
+
it "does not return duplicate fields if a field with a matching [form] attribute is also a child of the form", ->
|
44
|
+
form = fixture('form#form-id')
|
45
|
+
field = e.affix(form, 'input[type=text][form=form-id]')
|
46
|
+
results = up.form.fields(form)
|
47
|
+
expect(results).toMatchList([field])
|
48
|
+
|
49
|
+
|
9
50
|
describe 'up.observe', ->
|
10
51
|
|
11
52
|
beforeEach ->
|
@@ -858,6 +899,23 @@ describe 'up.form', ->
|
|
858
899
|
Trigger.change($field)
|
859
900
|
next => expect(submitSpy).toHaveBeenCalled()
|
860
901
|
|
902
|
+
it 'submits the form when a change is observed on a container for a radio button group', asyncSpec (next) ->
|
903
|
+
form = fixture('form')
|
904
|
+
group = e.affix(form, '.group[up-autosubmit][up-delay=0]')
|
905
|
+
radio1 = e.affix(group, 'input[type=radio][name=foo][value=1]')
|
906
|
+
radio2 = e.affix(group, 'input[type=radio][name=foo][value=2]')
|
907
|
+
up.hello(form)
|
908
|
+
submitSpy = up.form.knife.mock('submit').and.returnValue(Promise.reject())
|
909
|
+
Trigger.clickSequence(radio1)
|
910
|
+
next =>
|
911
|
+
expect(submitSpy.calls.count()).toBe(1)
|
912
|
+
Trigger.clickSequence(radio2)
|
913
|
+
next =>
|
914
|
+
expect(submitSpy.calls.count()).toBe(2)
|
915
|
+
Trigger.clickSequence(radio1)
|
916
|
+
next =>
|
917
|
+
expect(submitSpy.calls.count()).toBe(3)
|
918
|
+
|
861
919
|
describe 'form[up-autosubmit]', ->
|
862
920
|
|
863
921
|
it 'submits the form when a change is observed in any of its fields', asyncSpec (next) ->
|
@@ -1023,6 +1081,7 @@ describe 'up.form', ->
|
|
1023
1081
|
|
1024
1082
|
</form>
|
1025
1083
|
"""
|
1084
|
+
up.hello(container)
|
1026
1085
|
|
1027
1086
|
Trigger.change $('#registration input[name=password]')
|
1028
1087
|
|
@@ -1048,6 +1107,75 @@ describe 'up.form', ->
|
|
1048
1107
|
expect($labels[0]).not.toHaveText('Validation message')
|
1049
1108
|
expect($labels[1]).toHaveText('Validation message')
|
1050
1109
|
|
1110
|
+
describe 'form[up-validate]', ->
|
1111
|
+
|
1112
|
+
# it 'prints an error saying that this form is not yet supported', ->
|
1113
|
+
|
1114
|
+
it 'performs server-side validation for all fieldsets contained within the form', asyncSpec (next) ->
|
1115
|
+
container = fixture('.container')
|
1116
|
+
container.innerHTML = """
|
1117
|
+
<form action="/users" id="registration" up-validate>
|
1118
|
+
|
1119
|
+
<div up-fieldset>
|
1120
|
+
<input type="text" name="email">
|
1121
|
+
</div>
|
1122
|
+
|
1123
|
+
<div up-fieldset>
|
1124
|
+
<input type="password" name="password">
|
1125
|
+
</div>
|
1126
|
+
|
1127
|
+
</form>
|
1128
|
+
"""
|
1129
|
+
up.hello(container)
|
1130
|
+
|
1131
|
+
Trigger.change $('#registration input[name=password]')
|
1132
|
+
|
1133
|
+
next =>
|
1134
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
1135
|
+
expect(@lastRequest().requestHeaders['X-Up-Validate']).toEqual('password')
|
1136
|
+
expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('[up-fieldset]:has(input[name="password"])')
|
1137
|
+
|
1138
|
+
|
1139
|
+
@respondWith """
|
1140
|
+
<form action="/users" id="registration" up-validate>
|
1141
|
+
|
1142
|
+
<div up-fieldset>
|
1143
|
+
Validation message
|
1144
|
+
<input type="text" name="email">
|
1145
|
+
</div>
|
1146
|
+
|
1147
|
+
<div up-fieldset>
|
1148
|
+
Validation message
|
1149
|
+
<input type="password" name="password">
|
1150
|
+
</div>
|
1151
|
+
|
1152
|
+
</form>
|
1153
|
+
"""
|
1154
|
+
|
1155
|
+
next =>
|
1156
|
+
$labels = $('#registration [up-fieldset]')
|
1157
|
+
expect($labels[0]).not.toHaveText('Validation message')
|
1158
|
+
expect($labels[1]).toHaveText('Validation message')
|
1159
|
+
|
1160
|
+
it 'only sends a single request when a radio button group changes', asyncSpec (next) ->
|
1161
|
+
container = fixture('.container')
|
1162
|
+
container.innerHTML = """
|
1163
|
+
<form action="/users" id="registration" up-validate>
|
1164
|
+
|
1165
|
+
<div up-fieldset>
|
1166
|
+
<input type="radio" name="foo" value="1" checked>
|
1167
|
+
<input type="radio" name="foo" value="2">
|
1168
|
+
</div>
|
1169
|
+
|
1170
|
+
</form>
|
1171
|
+
"""
|
1172
|
+
up.hello(container)
|
1173
|
+
|
1174
|
+
Trigger.change $('#registration input[value="2"]')
|
1175
|
+
|
1176
|
+
next =>
|
1177
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
1178
|
+
|
1051
1179
|
describe '[up-switch]', ->
|
1052
1180
|
|
1053
1181
|
describe 'on a select', ->
|
@@ -2432,13 +2432,26 @@ describe 'up.fragment', ->
|
|
2432
2432
|
expect($element).toBeDetached()
|
2433
2433
|
|
2434
2434
|
it 'calls destructors for custom elements', (done) ->
|
2435
|
-
up.$compiler('.element', ($element) -> destructor)
|
2436
2435
|
destructor = jasmine.createSpy('destructor')
|
2436
|
+
up.$compiler('.element', ($element) -> destructor)
|
2437
2437
|
up.hello(fixture('.element'))
|
2438
2438
|
up.destroy('.element').then ->
|
2439
2439
|
expect(destructor).toHaveBeenCalled()
|
2440
2440
|
done()
|
2441
2441
|
|
2442
|
+
it 'does not call destructors twice if up.destroy() is called twice on the same fragment', asyncSpec (next) ->
|
2443
|
+
destructor = jasmine.createSpy('destructor')
|
2444
|
+
up.compiler('.element', (element) -> destructor)
|
2445
|
+
|
2446
|
+
element = fixture('.element')
|
2447
|
+
up.hello(element)
|
2448
|
+
|
2449
|
+
up.destroy(element, animation: 'fade-out', duration: 10)
|
2450
|
+
up.destroy(element, animation: 'fade-out', duration: 10)
|
2451
|
+
|
2452
|
+
next.after 60, ->
|
2453
|
+
expect(destructor.calls.count()).toBe(1)
|
2454
|
+
|
2442
2455
|
it 'marks the old element as .up-destroying before destructors', (done) ->
|
2443
2456
|
destructor = jasmine.createSpy('destructor')
|
2444
2457
|
up.$compiler '.container', ($element) ->
|
@@ -2452,6 +2465,19 @@ describe 'up.fragment', ->
|
|
2452
2465
|
expect(destructor).toHaveBeenCalledWith('old text', true)
|
2453
2466
|
done()
|
2454
2467
|
|
2468
|
+
it 'marks the old element as [aria-hidden=true] before destructors', (done) ->
|
2469
|
+
destructor = jasmine.createSpy('destructor')
|
2470
|
+
up.$compiler '.container', ($element) ->
|
2471
|
+
-> destructor($element.text(), $element.is('[aria-hidden=true]'))
|
2472
|
+
$container = $fixture('.container').text('old text')
|
2473
|
+
up.hello($container)
|
2474
|
+
|
2475
|
+
destroyDone = up.destroy('.container')
|
2476
|
+
|
2477
|
+
destroyDone.then ->
|
2478
|
+
expect(destructor).toHaveBeenCalledWith('old text', true)
|
2479
|
+
done()
|
2480
|
+
|
2455
2481
|
it 'marks the old element as .up-destroying before destructors after an { animation }', (done) ->
|
2456
2482
|
destructor = jasmine.createSpy('destructor')
|
2457
2483
|
up.$compiler '.container', ($element) ->
|
@@ -2533,6 +2559,15 @@ describe 'up.fragment', ->
|
|
2533
2559
|
)
|
2534
2560
|
expect($element).toBeDetached()
|
2535
2561
|
|
2562
|
+
it 'removes element-related data from the global jQuery cache (bugfix)', asyncSpec (next) ->
|
2563
|
+
$element = $fixture('.element')
|
2564
|
+
$element.data('foo', { foo: '1' })
|
2565
|
+
expect($element.data('foo')).toEqual({ foo: '1'})
|
2566
|
+
up.destroy($element)
|
2567
|
+
|
2568
|
+
next ->
|
2569
|
+
expect($element.data('foo')).toBeMissing()
|
2570
|
+
|
2536
2571
|
describe 'up.reload', ->
|
2537
2572
|
|
2538
2573
|
describeCapability 'canPushState', ->
|
@@ -928,9 +928,14 @@ describe 'up.link', ->
|
|
928
928
|
Trigger.mouseup(@$link)
|
929
929
|
next => expect(@followSpy).not.toHaveBeenCalled()
|
930
930
|
|
931
|
-
it 'does nothing on click', asyncSpec (next)->
|
931
|
+
it 'does nothing on click if there was an earlier mousedown event', asyncSpec (next)->
|
932
|
+
Trigger.mousedown(@$link)
|
932
933
|
Trigger.click(@$link)
|
933
|
-
next => expect(@followSpy
|
934
|
+
next => expect(@followSpy.calls.count()).toBe(1)
|
935
|
+
|
936
|
+
it 'does follow a link on click if there was never a mousedown event (e.g. if the user pressed enter)', asyncSpec (next) ->
|
937
|
+
Trigger.click(@$link)
|
938
|
+
next => expect(@followSpy.calls.mostRecent().args[0]).toEqual(@$link[0])
|
934
939
|
|
935
940
|
# IE does not call JavaScript and always performs the default action on right clicks
|
936
941
|
unless AgentDetector.isIE() || AgentDetector.isEdge()
|
@@ -532,8 +532,9 @@ describe 'up.modal', ->
|
|
532
532
|
next => expect(@followSpy).not.toHaveBeenCalled()
|
533
533
|
|
534
534
|
it 'does nothing on click', asyncSpec (next) ->
|
535
|
+
Trigger.mousedown(@$link)
|
535
536
|
Trigger.click(@$link)
|
536
|
-
next => expect(@followSpy
|
537
|
+
next => expect(@followSpy.calls.count()).toBe(1)
|
537
538
|
|
538
539
|
# IE does not call JavaScript and always performs the default action on right clicks
|
539
540
|
unless AgentDetector.isIE() || AgentDetector.isEdge()
|
@@ -562,6 +563,27 @@ describe 'up.modal', ->
|
|
562
563
|
next =>
|
563
564
|
expect(@lastRequest().method).toEqual 'POST'
|
564
565
|
|
566
|
+
describe 'with [up-cache] modifier', ->
|
567
|
+
it 'honours the given setting', asyncSpec (next) ->
|
568
|
+
$link = $fixture('a[href="/path"][up-modal=".target"][up-cache="false"]')
|
569
|
+
Trigger.clickSequence($link)
|
570
|
+
|
571
|
+
next =>
|
572
|
+
@respondWith('<div class="target">modal content 1</div>')
|
573
|
+
|
574
|
+
next =>
|
575
|
+
expect('.up-modal .target').toHaveText('modal content 1')
|
576
|
+
history.back()
|
577
|
+
|
578
|
+
next =>
|
579
|
+
Trigger.clickSequence($link)
|
580
|
+
|
581
|
+
next =>
|
582
|
+
@respondWith('<div class="target">modal content 2</div>')
|
583
|
+
|
584
|
+
next =>
|
585
|
+
expect('.up-modal .target').toHaveText('modal content 2')
|
586
|
+
|
565
587
|
it 'adds a history entry and allows the user to use the back button', asyncSpec (next) ->
|
566
588
|
up.motion.config.enabled = false
|
567
589
|
up.history.config.enabled = true
|
@@ -232,8 +232,9 @@ describe 'up.popup', ->
|
|
232
232
|
next => expect(@attachSpy).not.toHaveBeenCalled()
|
233
233
|
|
234
234
|
it 'does nothing on click', asyncSpec (next) ->
|
235
|
+
Trigger.mousedown(@$link)
|
235
236
|
Trigger.click(@$link)
|
236
|
-
next => expect(@attachSpy
|
237
|
+
next => expect(@attachSpy.calls.count()).toBe(1)
|
237
238
|
|
238
239
|
# IE does not call JavaScript and always performs the default action on right clicks
|
239
240
|
unless AgentDetector.isIE() || AgentDetector.isEdge()
|
@@ -1404,3 +1404,31 @@ describe 'up.util', ->
|
|
1404
1404
|
it 'returns false for NaN', ->
|
1405
1405
|
value = NaN
|
1406
1406
|
expect(up.util.isList(value)).toBe(false)
|
1407
|
+
|
1408
|
+
describe 'up.util.isJQuery', ->
|
1409
|
+
|
1410
|
+
it 'returns true for a jQuery collection', ->
|
1411
|
+
value = $('body')
|
1412
|
+
expect(up.util.isJQuery(value)).toBe(true)
|
1413
|
+
|
1414
|
+
it 'returns false for a native element', ->
|
1415
|
+
value = document.body
|
1416
|
+
expect(up.util.isJQuery(value)).toBe(false)
|
1417
|
+
|
1418
|
+
it 'returns false (and does not crash) for undefined', ->
|
1419
|
+
value = undefined
|
1420
|
+
expect(up.util.isJQuery(value)).toBe(false)
|
1421
|
+
|
1422
|
+
describe 'up.util.escapeHtml', ->
|
1423
|
+
|
1424
|
+
it 'escapes double quotes', ->
|
1425
|
+
result = up.util.escapeHtml('before"after')
|
1426
|
+
expect(result).toEqual('before"after')
|
1427
|
+
|
1428
|
+
it 'escapes single quotes', ->
|
1429
|
+
result = up.util.escapeHtml("before'after")
|
1430
|
+
expect(result).toEqual('before'after')
|
1431
|
+
|
1432
|
+
it 'escapes angle brackets', ->
|
1433
|
+
result = up.util.escapeHtml('before<script>after')
|
1434
|
+
expect(result).toEqual('before<script>after')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unpoly-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -232,6 +232,7 @@ files:
|
|
232
232
|
- spec_app/app/views/replace_test/_nav.erb
|
233
233
|
- spec_app/app/views/replace_test/page1.erb
|
234
234
|
- spec_app/app/views/replace_test/page2.erb
|
235
|
+
- spec_app/app/views/replace_test/table.erb
|
235
236
|
- spec_app/app/views/reveal_test/long1.erb
|
236
237
|
- spec_app/app/views/reveal_test/long2.erb
|
237
238
|
- spec_app/app/views/reveal_test/within_document_viewport.erb
|
@@ -399,8 +400,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
399
400
|
- !ruby/object:Gem::Version
|
400
401
|
version: '0'
|
401
402
|
requirements: []
|
402
|
-
|
403
|
-
rubygems_version: 2.7.6
|
403
|
+
rubygems_version: 3.2.16
|
404
404
|
signing_key:
|
405
405
|
specification_version: 4
|
406
406
|
summary: Rails bindings for Unpoly, the unobtrusive JavaScript framework
|