unpoly-rails 0.60.3 → 1.0.0

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

Potentially problematic release.


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

Files changed (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