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.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -2
  3. data/CHANGELOG.md +84 -0
  4. data/README.md +1 -1
  5. data/Rakefile +11 -1
  6. data/dist/unpoly.js +226 -91
  7. data/dist/unpoly.min.js +4 -4
  8. data/lib/assets/javascripts/unpoly/browser.coffee.erb +10 -5
  9. data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +21 -12
  10. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +2 -2
  11. data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +1 -1
  12. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +11 -3
  13. data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +1 -0
  14. data/lib/assets/javascripts/unpoly/element.coffee.erb +9 -6
  15. data/lib/assets/javascripts/unpoly/event.coffee.erb +12 -4
  16. data/lib/assets/javascripts/unpoly/form.coffee.erb +85 -14
  17. data/lib/assets/javascripts/unpoly/fragment.coffee.erb +7 -3
  18. data/lib/assets/javascripts/unpoly/framework.coffee +7 -9
  19. data/lib/assets/javascripts/unpoly/link.coffee.erb +1 -1
  20. data/lib/assets/javascripts/unpoly/log.coffee +6 -2
  21. data/lib/assets/javascripts/unpoly/modal.coffee.erb +4 -2
  22. data/lib/assets/javascripts/unpoly/motion.coffee.erb +1 -1
  23. data/lib/assets/javascripts/unpoly/protocol.coffee +1 -1
  24. data/lib/assets/javascripts/unpoly/syntax.coffee.erb +3 -4
  25. data/lib/assets/javascripts/unpoly/util.coffee.erb +4 -2
  26. data/lib/assets/javascripts/unpoly/viewport.coffee.erb +26 -2
  27. data/lib/unpoly/rails/version.rb +1 -1
  28. data/package.json +1 -1
  29. data/spec_app/Gemfile +2 -4
  30. data/spec_app/Gemfile.lock +23 -27
  31. data/spec_app/app/views/compiler_test/timestamp.erb +1 -0
  32. data/spec_app/app/views/css_test/modal.erb +1 -1
  33. data/spec_app/app/views/css_test/popup.erb +1 -1
  34. data/spec_app/app/views/pages/start.erb +2 -1
  35. data/spec_app/app/views/replace_test/table.erb +16 -0
  36. data/spec_app/spec/javascripts/up/event_spec.js.coffee +34 -0
  37. data/spec_app/spec/javascripts/up/form_spec.js.coffee +128 -0
  38. data/spec_app/spec/javascripts/up/fragment_spec.js.coffee +36 -1
  39. data/spec_app/spec/javascripts/up/link_spec.js.coffee +7 -2
  40. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -1
  41. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +2 -1
  42. data/spec_app/spec/javascripts/up/util_spec.js.coffee +28 -0
  43. 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).not.toHaveBeenCalled()
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).not.toHaveBeenCalled()
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).not.toHaveBeenCalled()
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&quot;after')
1427
+
1428
+ it 'escapes single quotes', ->
1429
+ result = up.util.escapeHtml("before'after")
1430
+ expect(result).toEqual('before&#x27;after')
1431
+
1432
+ it 'escapes angle brackets', ->
1433
+ result = up.util.escapeHtml('before<script>after')
1434
+ expect(result).toEqual('before&lt;script&gt;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.60.3
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: 2019-08-03 00:00:00.000000000 Z
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
- rubyforge_project:
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