unpoly-rails 0.60.2 → 0.62.1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -2
  3. data/CHANGELOG.md +105 -0
  4. data/Rakefile +5 -1
  5. data/dist/unpoly.js +199 -80
  6. data/dist/unpoly.min.js +4 -4
  7. data/lib/assets/javascripts/unpoly/browser.coffee.erb +10 -5
  8. data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +21 -12
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +2 -2
  10. data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +1 -1
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +11 -3
  12. data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +2 -1
  13. data/lib/assets/javascripts/unpoly/element.coffee.erb +4 -4
  14. data/lib/assets/javascripts/unpoly/event.coffee.erb +12 -4
  15. data/lib/assets/javascripts/unpoly/form.coffee.erb +77 -15
  16. data/lib/assets/javascripts/unpoly/fragment.coffee.erb +7 -3
  17. data/lib/assets/javascripts/unpoly/link.coffee.erb +1 -1
  18. data/lib/assets/javascripts/unpoly/modal.coffee.erb +4 -2
  19. data/lib/assets/javascripts/unpoly/motion.coffee.erb +1 -1
  20. data/lib/assets/javascripts/unpoly/protocol.coffee +1 -1
  21. data/lib/assets/javascripts/unpoly/syntax.coffee.erb +3 -4
  22. data/lib/assets/javascripts/unpoly/util.coffee.erb +2 -1
  23. data/lib/assets/javascripts/unpoly/viewport.coffee.erb +26 -2
  24. data/lib/unpoly/rails/version.rb +1 -1
  25. data/package.json +1 -1
  26. data/spec_app/Gemfile +2 -4
  27. data/spec_app/Gemfile.lock +23 -27
  28. data/spec_app/app/views/css_test/modal.erb +1 -1
  29. data/spec_app/app/views/css_test/popup.erb +1 -1
  30. data/spec_app/app/views/pages/start.erb +2 -1
  31. data/spec_app/app/views/replace_test/table.erb +16 -0
  32. data/spec_app/spec/javascripts/up/event_spec.js.coffee +34 -0
  33. data/spec_app/spec/javascripts/up/form_spec.js.coffee +137 -9
  34. data/spec_app/spec/javascripts/up/fragment_spec.js.coffee +36 -1
  35. data/spec_app/spec/javascripts/up/link_spec.js.coffee +7 -2
  36. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -1
  37. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +2 -1
  38. data/spec_app/spec/javascripts/up/util_spec.js.coffee +14 -0
  39. metadata +4 -4
@@ -250,6 +250,7 @@ up.modal = do ->
250
250
  createHiddenFrame = (target, options) ->
251
251
  html = templateHtml()
252
252
  state.modalElement = modalElement = e.createFromHtml(html)
253
+ modalElement.setAttribute('aria-modal', 'true') # tell modern screen readers to make all other elements inert
253
254
  modalElement.setAttribute('up-flavor', state.flavor)
254
255
  modalElement.setAttribute('up-position', state.position) if u.isPresent(state.position)
255
256
 
@@ -288,7 +289,7 @@ up.modal = do ->
288
289
  var link = document.querySelector('a')
289
290
  up.modal.follow(link)
290
291
 
291
- Any option attributes for [`a[up-modal]`](/a.up-modal) will be honored.
292
+ Any option attributes for [`a[up-modal]`](/a-up-modal) will be honored.
292
293
 
293
294
  Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
294
295
 
@@ -436,6 +437,7 @@ up.modal = do ->
436
437
  options.layer = 'modal'
437
438
  options.failTarget ?= link.getAttribute('up-fail-target')
438
439
  options.failLayer ?= link.getAttribute('up-fail-layer') ? 'auto'
440
+ options.cache ?= e.booleanAttr(link, 'up-cache')
439
441
 
440
442
  animateOptions = up.motion.animateOptions(options, link, duration: flavorDefault('openDuration', options.flavor), easing: flavorDefault('openEasing', options.flavor))
441
443
 
@@ -650,7 +652,7 @@ up.modal = do ->
650
652
 
651
653
  Clicking would request the path `/blog` and select `.blog-list` from
652
654
  the HTML response. Unpoly will dim the page
653
- and place the matching `.blog-list` tag will be placed in
655
+ and place the matching `.blog-list` tag in
654
656
  a modal dialog.
655
657
 
656
658
  @selector a[up-modal]
@@ -99,7 +99,7 @@ up.motion = do ->
99
99
 
100
100
  You can pass additional options:
101
101
 
102
- up.animate('warning', '.fade-in', {
102
+ up.animate('.warning', 'fade-in', {
103
103
  delay: 1000,
104
104
  duration: 250,
105
105
  easing: 'linear'
@@ -246,7 +246,7 @@ up.protocol = do ->
246
246
  The parameter name can be configured as a string or as function that returns the parameter name.
247
247
  If no name is set, no token will be sent.
248
248
 
249
- Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
249
+ Defaults to the `content` attribute of a `<meta>` tag named `csrf-param`:
250
250
 
251
251
  <meta name="csrf-param" content="authenticity_token" />
252
252
 
@@ -215,7 +215,7 @@ up.syntax = do ->
215
215
  ###**
216
216
  Registers a [compiler](/up.compiler) that is run before all other compilers.
217
217
 
218
- Use `up.macro()` to register a compiler that sets multiply Unpoly attributes.
218
+ Use `up.macro()` to register a compiler that sets multiple Unpoly attributes.
219
219
 
220
220
  \#\#\# Example
221
221
 
@@ -373,10 +373,9 @@ up.syntax = do ->
373
373
  clean = (fragment) ->
374
374
  cleanables = e.subtree(fragment, '.up-can-clean')
375
375
  u.each cleanables, (cleanable) ->
376
- if destructors = cleanable.upDestructors
376
+ if destructors = u.pluckKey(cleanable, 'upDestructors')
377
377
  destructor() for destructor in destructors
378
- # We do not actually remove the #upDestructors property or .up-can-* classes for performance reasons.
379
- # The element we just cleaned is about to be removed from the DOM.
378
+ cleanable.classList.remove('up-can-clean')
380
379
 
381
380
  ###**
382
381
  Checks if the given element has an [`up-data`](/up-data) attribute.
@@ -499,7 +499,8 @@ up.util = do ->
499
499
  @stable
500
500
  ###
501
501
  isJQuery = (object) ->
502
- up.browser.canJQuery() && (object instanceof jQuery)
502
+ # We cannot do `object instanceof jQuery` since window.jQuery might not be set
503
+ !!object?.jquery
503
504
 
504
505
  ###**
505
506
  Returns whether the given argument is an object with a `then` method.
@@ -506,13 +506,37 @@ up.viewport = do ->
506
506
 
507
507
  The scroll positions will be associated with the current URL.
508
508
  They can later be restored by calling [`up.viewport.restoreScroll()`](/up.viewport.restoreScroll)
509
- at the same URL.
509
+ at the same URL, or by following a link with an [`[up-restore-scroll]`](/a-up-follow#up-restore-scroll)
510
+ attribute.
510
511
 
511
- Unpoly automatically saves scroll positions whenever a fragment was updated on the page.
512
+ Unpoly automatically saves scroll positions before a [fragment update](/up.replace)
513
+ you will rarely need to call this function yourself.
514
+
515
+ \#\#\# Examples
516
+
517
+ Should you need to save the current scroll positions outside of a [fragment update](/up.replace),
518
+ you may call:
519
+
520
+ up.viewport.saveScroll()
521
+
522
+ Instead of saving the current scroll positions for the current URL, you may also pass another
523
+ url or vertical scroll positionsfor each viewport:
524
+
525
+ up.viewport.saveScroll({
526
+ url: '/inbox',
527
+ tops: {
528
+ 'body': 0,
529
+ '.sidebar', 100,
530
+ '.main', 320
531
+ }
532
+ })
512
533
 
513
534
  @function up.viewport.saveScroll
514
535
  @param {string} [options.url]
536
+ The URL for which to save scroll positions.
537
+ If omitted, the current browser location is used.
515
538
  @param {Object<string, number>} [options.tops]
539
+ An object mapping viewport selectors to vertical scroll positions in pixels.
516
540
  @experimental
517
541
  ###
518
542
  saveScroll = (options = {}) ->
@@ -4,6 +4,6 @@ module Unpoly
4
4
  # The current version of the unpoly-rails gem.
5
5
  # This version number is also used for releases of the Unpoly
6
6
  # frontend code.
7
- VERSION = '0.60.2'
7
+ VERSION = '0.62.1'
8
8
  end
9
9
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unpoly",
3
- "version": "0.60.2",
3
+ "version": "0.62.1",
4
4
  "description": "Unobtrusive JavaScript framework",
5
5
  "main": "dist/unpoly.js",
6
6
  "files": [
@@ -14,11 +14,9 @@ gem 'bower-rails'
14
14
  gem 'bootstrap-sass', '~> 3.3'
15
15
  gem 'rake', '< 11'
16
16
 
17
- # Jasmine spec runner won't boot with a more modern version of sprockets.
18
- # It crashes with an "asset not precompiled" error.
19
17
  gem 'tilt', '=1.4.1'
20
- gem 'sprockets-rails', '=2.2.4'
21
- gem 'sprockets', '=2.12.3'
18
+ gem 'sprockets-rails', '~> 3.2.1'
19
+ gem 'sprockets', '~> 3.7.2'
22
20
 
23
21
  group :development, :test do
24
22
  gem 'byebug'
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: ..
14
14
  specs:
15
- unpoly-rails (0.60.1)
15
+ unpoly-rails (0.62.0)
16
16
  rails (>= 3)
17
17
 
18
18
  GEM
@@ -62,7 +62,7 @@ GEM
62
62
  autoprefixer-rails (>= 5.2.1)
63
63
  sass (>= 3.3.4)
64
64
  bower-rails (0.9.2)
65
- builder (3.2.3)
65
+ builder (3.2.4)
66
66
  byebug (3.5.1)
67
67
  columnize (~> 0.8)
68
68
  debugger-linecache (~> 1.2)
@@ -75,8 +75,8 @@ GEM
75
75
  execjs
76
76
  coffee-script-source (1.12.2)
77
77
  columnize (0.9.0)
78
- concurrent-ruby (1.1.4)
79
- crass (1.0.4)
78
+ concurrent-ruby (1.1.6)
79
+ crass (1.0.6)
80
80
  debug_inspector (0.0.2)
81
81
  debugger-linecache (1.2.0)
82
82
  diff-lcs (1.2.5)
@@ -92,7 +92,6 @@ GEM
92
92
  haml (>= 3.1, < 5.0)
93
93
  html2haml (>= 1.0.1)
94
94
  railties (>= 4.0.1)
95
- hike (1.2.3)
96
95
  hpricot (0.8.6)
97
96
  html2haml (1.0.1)
98
97
  erubis (~> 2.7.0)
@@ -102,25 +101,24 @@ GEM
102
101
  i18n (0.9.5)
103
102
  concurrent-ruby (~> 1.0)
104
103
  jasmine-core (2.99.2)
105
- jquery-rails (4.3.1)
104
+ jquery-rails (4.3.5)
106
105
  rails-dom-testing (>= 1, < 3)
107
106
  railties (>= 4.2.0)
108
107
  thor (>= 0.14, < 2.0)
109
108
  json (1.8.6)
110
109
  libv8 (3.16.14.3)
111
- loofah (2.2.3)
110
+ loofah (2.4.0)
112
111
  crass (~> 1.0.2)
113
112
  nokogiri (>= 1.5.9)
114
113
  mail (2.6.3)
115
114
  mime-types (>= 1.16, < 3)
116
115
  mime-types (2.99)
117
116
  mini_portile2 (2.4.0)
118
- minitest (5.11.3)
119
- multi_json (1.13.1)
120
- nokogiri (1.9.1)
117
+ minitest (5.14.0)
118
+ nokogiri (1.10.9)
121
119
  mini_portile2 (~> 2.4.0)
122
120
  phantomjs (2.1.1.0)
123
- rack (1.6.11)
121
+ rack (1.6.13)
124
122
  rack-test (0.6.3)
125
123
  rack (>= 1.0)
126
124
  rails (4.2.0)
@@ -140,8 +138,8 @@ GEM
140
138
  activesupport (>= 4.2.0, < 5.0)
141
139
  nokogiri (~> 1.6)
142
140
  rails-deprecated_sanitizer (>= 1.0.1)
143
- rails-html-sanitizer (1.0.4)
144
- loofah (~> 2.2, >= 2.2.2)
141
+ rails-html-sanitizer (1.3.0)
142
+ loofah (~> 2.3)
145
143
  railties (4.2.0)
146
144
  actionpack (= 4.2.0)
147
145
  activesupport (= 4.2.0)
@@ -177,23 +175,21 @@ GEM
177
175
  tilt (>= 1.1, < 3)
178
176
  sexp_processor (4.4.4)
179
177
  slop (3.6.0)
180
- sprockets (2.12.3)
181
- hike (~> 1.2)
182
- multi_json (~> 1.0)
183
- rack (~> 1.0)
184
- tilt (~> 1.1, != 1.3.0)
185
- sprockets-rails (2.2.4)
186
- actionpack (>= 3.0)
187
- activesupport (>= 3.0)
188
- sprockets (>= 2.8, < 4.0)
178
+ sprockets (3.7.2)
179
+ concurrent-ruby (~> 1.0)
180
+ rack (> 1, < 3)
181
+ sprockets-rails (3.2.1)
182
+ actionpack (>= 4.0)
183
+ activesupport (>= 4.0)
184
+ sprockets (>= 3.0.0)
189
185
  sqlite3 (1.3.10)
190
186
  therubyracer (0.12.1)
191
187
  libv8 (~> 3.16.14.0)
192
188
  ref
193
- thor (0.20.3)
189
+ thor (1.0.1)
194
190
  thread_safe (0.3.6)
195
191
  tilt (1.4.1)
196
- tzinfo (1.2.5)
192
+ tzinfo (1.2.7)
197
193
  thread_safe (~> 0.1)
198
194
  uglifier (2.6.0)
199
195
  execjs (>= 0.3.0)
@@ -220,8 +216,8 @@ DEPENDENCIES
220
216
  rake (< 11)
221
217
  rspec-rails
222
218
  sass-rails (~> 5.0)
223
- sprockets (= 2.12.3)
224
- sprockets-rails (= 2.2.4)
219
+ sprockets (~> 3.7.2)
220
+ sprockets-rails (~> 3.2.1)
225
221
  sqlite3
226
222
  therubyracer
227
223
  tilt (= 1.4.1)
@@ -230,4 +226,4 @@ DEPENDENCIES
230
226
  web-console (~> 2.0)
231
227
 
232
228
  BUNDLED WITH
233
- 1.16.4
229
+ 1.17.3
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  var u = up.util;
3
- up.compiler('.clamped-open', function($link) {
3
+ up.$compiler('.clamped-open', function($link) {
4
4
  $link.on('click', function(event) {
5
5
  event.preventDefault();
6
6
  u.task(function() {
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  var u = up.util;
3
- up.compiler('.clamped-open', function(link) {
3
+ up.$compiler('.clamped-open', function(link) {
4
4
  link.addEventListener('click', function(event) {
5
5
  event.preventDefault();
6
6
  u.task(function() {
@@ -63,7 +63,8 @@
63
63
  <li><%= link_to 'Form (redirect)', '/form_test/redirect/new' %></li>
64
64
  <li><%= link_to 'Error', '/error_test/trigger' %></li>
65
65
  <li><%= link_to 'Booting with non-GET method', '/method_test/page1' %></li>
66
- <li><%= link_to 'Fragment update', '/replace_test/page1' %></li>
66
+ <li><%= link_to 'Fragment update (basic)', '/replace_test/page1' %></li>
67
+ <li><%= link_to 'Fragment update (table)', '/replace_test/table' %></li>
67
68
  <li><%= link_to 'Hash links (without Unpoly)', '/hash_test/vanilla#one' %></li>
68
69
  <li><%= link_to 'Hash links (with Unpoly)', '/hash_test/unpoly#one' %></li>
69
70
  <li><%= link_to 'Revealing content in an overflowing <div> ', '/reveal_test/within_overflowing_div_viewport' %></li>
@@ -0,0 +1,16 @@
1
+ <table>
2
+ <tbody>
3
+ <tr id="one">
4
+ <td><%= Time.now %></td>
5
+ <td><a href="table" up-target="#one">Replace this row</a></td>
6
+ </tr>
7
+ <tr id="two">
8
+ <td><%= Time.now %></td>
9
+ <td><a href="table" up-target="#two">Replace this row</a></td>
10
+ </tr>
11
+ <tr id="three">
12
+ <td><%= Time.now %></td>
13
+ <td><a href="table" up-target="#three">Replace this row</a></td>
14
+ </tr>
15
+ </tbody>
16
+ </table>
@@ -494,3 +494,37 @@ describe 'up.event', ->
494
494
  promiseState(promise).then (result) ->
495
495
  expect(result.state).toEqual('rejected')
496
496
  done()
497
+
498
+ describe 'up.event.halt', ->
499
+
500
+ it 'stops propagation of the given event to other event listeners on the same element', ->
501
+ otherListenerBefore = jasmine.createSpy()
502
+ otherListenerAfter = jasmine.createSpy()
503
+ element = fixture('div')
504
+
505
+ element.addEventListener('foo', otherListenerBefore)
506
+ element.addEventListener('foo', up.event.halt)
507
+ element.addEventListener('foo', otherListenerAfter)
508
+
509
+ up.emit(element, 'foo')
510
+
511
+ expect(otherListenerBefore).toHaveBeenCalled()
512
+ expect(otherListenerAfter).not.toHaveBeenCalled()
513
+
514
+ it 'stops the event from bubbling up the document tree', ->
515
+ parent = fixture('div')
516
+ element = e.affix(parent, 'div')
517
+ parentListener = jasmine.createSpy()
518
+ parent.addEventListener('foo', parentListener)
519
+ element.addEventListener('foo', up.event.halt)
520
+
521
+ up.emit(element, 'foo')
522
+
523
+ expect(parentListener).not.toHaveBeenCalled()
524
+
525
+ it 'prevents default on the event', ->
526
+ element = fixture('div')
527
+ element.addEventListener('foo', up.event.halt)
528
+ event = up.emit(element, 'foo')
529
+ expect(event.defaultPrevented).toBe(true)
530
+
@@ -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) ->
@@ -1013,16 +1071,17 @@ describe 'up.form', ->
1013
1071
  container.innerHTML = """
1014
1072
  <form action="/users" id="registration">
1015
1073
 
1016
- <label>
1074
+ <div up-fieldset>
1017
1075
  <input type="text" name="email" up-validate />
1018
- </label>
1076
+ </div>
1019
1077
 
1020
- <label>
1078
+ <div up-fieldset>
1021
1079
  <input type="password" name="password" up-validate />
1022
- </label>
1080
+ </div>
1023
1081
 
1024
1082
  </form>
1025
1083
  """
1084
+ up.hello(container)
1026
1085
 
1027
1086
  Trigger.change $('#registration input[name=password]')
1028
1087
 
@@ -1030,24 +1089,93 @@ describe 'up.form', ->
1030
1089
  @respondWith """
1031
1090
  <form action="/users" id="registration">
1032
1091
 
1033
- <label>
1092
+ <div up-fieldset>
1034
1093
  Validation message
1035
1094
  <input type="text" name="email" up-validate />
1036
- </label>
1095
+ </div>
1037
1096
 
1038
- <label>
1097
+ <div up-fieldset>
1039
1098
  Validation message
1040
1099
  <input type="password" name="password" up-validate />
1041
- </label>
1100
+ </div>
1042
1101
 
1043
1102
  </form>
1044
1103
  """
1045
1104
 
1046
1105
  next =>
1047
- $labels = $('#registration label')
1106
+ $labels = $('#registration [up-fieldset]')
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', ->