unpoly-rails 0.60.2 → 0.62.1

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 (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', ->