unpoly-rails 0.52.0 → 0.53.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.

@@ -5,7 +5,7 @@ class up.ExtractPlan
5
5
  constructor: (selector, options) ->
6
6
  @origin = options.origin
7
7
  @selector = up.dom.resolveSelector(selector, options.origin)
8
- @transition = options.transition || options.animation || 'none'
8
+ @transition = options.transition
9
9
  @response = options.response
10
10
  @oldLayer = options.layer
11
11
  @steps = @parseSteps()
@@ -41,16 +41,11 @@ class up.ExtractPlan
41
41
  ]
42
42
  ###
43
43
  parseSteps: =>
44
- if u.isString(@transition)
45
- transitions = @transition.split(comma)
46
- else
47
- transitions = [@transition]
48
-
49
44
  comma = /\ *,\ */
50
45
 
51
46
  disjunction = @selector.split(comma)
52
47
 
53
- u.map disjunction, (literal, i) ->
48
+ u.map disjunction, (literal, i) =>
54
49
  literalParts = literal.match(/^(.+?)(?:\:(before|after))?$/)
55
50
  literalParts or up.fail('Could not parse selector literal "%s"', literal)
56
51
  selector = literalParts[1]
@@ -60,8 +55,7 @@ class up.ExtractPlan
60
55
  selector = 'body'
61
56
 
62
57
  pseudoClass = literalParts[2]
63
- transition = transitions[i] || u.last(transitions)
64
58
 
65
59
  selector: selector
66
60
  pseudoClass: pseudoClass
67
- transition: transition
61
+ transition: @transition
@@ -28,13 +28,15 @@ up.dom = (($) ->
28
28
  It is recommend to always keep `'body'` as the last selector in the last in the case
29
29
  your server or load balancer renders an error message that does not contain your
30
30
  application layout.
31
- @param {string} [options.fallbackTransition='none']
32
- The transition to use when using a fallback target.
31
+ @param {string} [options.fallbackTransition=null]
32
+ The transition to use when using a [fallback target](/#options.fallbacks).
33
+
34
+ By default this is not set and the original replacement's transition is used.
33
35
  @stable
34
36
  ###
35
37
  config = u.config
36
38
  fallbacks: ['body']
37
- fallbackTransition: 'none'
39
+ fallbackTransition: null
38
40
 
39
41
  reset = ->
40
42
  config.reset()
@@ -187,8 +189,11 @@ up.dom = (($) ->
187
189
  If set to `false`, the history will remain unchanged.
188
190
  @param {boolean|string} [options.source=true]
189
191
  @param {boolean|string} [options.reveal=false]
190
- Whether to [reveal](/up.reveal) the element being updated, by
191
- scrolling its containing viewport.
192
+ Whether to [reveal](/up.reveal) the new fragment.
193
+
194
+ You can also pass a CSS selector for the element to reveal.
195
+ @param {boolean|string} [options.failReveal=false]
196
+ Whether to [reveal](/up.reveal) the new fragment when the server responds with an error.
192
197
 
193
198
  You can also pass a CSS selector for the element to reveal.
194
199
  @param {boolean} [options.restoreScroll=false]
@@ -239,8 +244,12 @@ up.dom = (($) ->
239
244
  failureOptions = u.merge options,
240
245
  humanizedTarget: 'failure target'
241
246
  provideTarget: undefined # don't provide a target if we're targeting the failTarget
247
+ restoreScroll: false
248
+ hungry: false
249
+
242
250
  u.renameKey(failureOptions, 'failTransition', 'transition')
243
251
  u.renameKey(failureOptions, 'failLayer', 'layer')
252
+ u.renameKey(failureOptions, 'failReveal', 'reveal')
244
253
 
245
254
  try
246
255
  improvedTarget = bestPreflightSelector(selectorOrElement, successOptions)
@@ -102,8 +102,14 @@ up.form = (($) ->
102
102
  The delay before the transition starts. See [`up.morph()`](/up.morph).
103
103
  @param {string} [options.easing]
104
104
  The timing function that controls the transition's acceleration. [`up.morph()`](/up.morph).
105
- @param {Element|jQuery|string} [options.reveal=true]
106
- Whether to reveal the target element within its viewport.
105
+ @param {Element|string} [options.reveal=true]
106
+ Whether to reveal the target fragment after it was replaced.
107
+
108
+ You can also pass a CSS selector for the element to reveal.
109
+ @param {boolean|string} [options.failReveal=true]
110
+ Whether to [reveal](/up.reveal) the target fragment when the server responds with an error.
111
+
112
+ You can also pass a CSS selector for the element to reveal.
107
113
  @param {boolean} [options.restoreScroll]
108
114
  If set to `true`, this will attempt to [`restore scroll positions`](/up.restoreScroll)
109
115
  previously seen on the destination URL.
@@ -135,13 +141,14 @@ up.form = (($) ->
135
141
  target = u.option(options.target, $form.attr('up-target'), 'body')
136
142
  url = u.option(options.url, $form.attr('action'), up.browser.url())
137
143
  options.failTarget = u.option(options.failTarget, $form.attr('up-fail-target')) || u.selectorForElement($form)
144
+ options.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true)
145
+ options.failReveal = u.option(options.failReveal, u.castedAttr($form, 'up-fail-reveal'), true)
138
146
  options.fallback = u.option(options.fallback, $form.attr('up-fallback'))
139
147
  options.history = u.option(options.history, u.castedAttr($form, 'up-history'), true)
140
148
  options.transition = u.option(options.transition, u.castedAttr($form, 'up-transition'), 'none')
141
149
  options.failTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), 'none')
142
150
  options.method = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase()
143
151
  options.headers = u.option(options.headers, {})
144
- options.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true)
145
152
  options.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
146
153
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
147
154
  options.origin = u.option(options.origin, $form)
@@ -520,7 +527,7 @@ up.form = (($) ->
520
527
  If omitted, Unpoly will replace the `<form>` tag itself, assuming that the
521
528
  server has echoed the form with validation errors.
522
529
  @param [up-fallback]
523
- The selector to replace if the server responds with a non-200 status code.
530
+ The selector to replace if the server responds with an error.
524
531
  @param {string} [up-transition]
525
532
  The animation to use when the form is replaced after a successful submission.
526
533
  @param {string} [up-fail-transition]
@@ -549,7 +556,18 @@ up.form = (($) ->
549
556
  The name of the layer that ought to be updated if the server sends a
550
557
  non-200 status code.
551
558
  @param {string} [up-reveal='true']
552
- Whether to reveal the target element within its viewport before updating.
559
+ Whether to reveal the target element after it was replaced.
560
+
561
+ You can also pass a CSS selector for the element to reveal.
562
+ @param {string} [up-fail-reveal='true']
563
+ Whether to reveal the target element when the server responds with an error.
564
+
565
+ You can also pass a CSS selector for the element to reveal. You may use this, for example,
566
+ to reveal the first validation error message:
567
+
568
+ <form up-target=".content" up-fail-reveal=".error">
569
+ ...
570
+ </form>
553
571
  @param {string} [up-restore-scroll='false']
554
572
  Whether to restore previously known scroll position of all viewports
555
573
  within the target selector.
@@ -237,7 +237,7 @@ up.layout = (($) ->
237
237
  @stable
238
238
  ###
239
239
  reveal = (elementOrSelector, options) ->
240
- $element = $(elementOrSelector)
240
+ $element = $(elementOrSelector).first() # we can only reveal one element
241
241
  up.puts 'Revealing fragment %o', $element.get(0)
242
242
  options = u.options(options)
243
243
 
@@ -473,7 +473,13 @@ up.layout = (($) ->
473
473
  selector = revealSelector(options.reveal)
474
474
  $element = up.first(selector) || $element
475
475
  revealOptions.top = true
476
- return reveal($element, revealOptions)
476
+
477
+ # If selectorOrElement was a CSS selector, don't blow up by calling reveal()
478
+ # with an empty jQuery collection. This might happen if a failed form submission
479
+ # reveals the first validation error message, but the error is shown in an
480
+ # unexpected element.
481
+ if $element.length
482
+ return reveal($element, revealOptions)
477
483
 
478
484
  # If we didn't need to scroll above, just return a resolved promise
479
485
  # to fulfill this function's signature.
@@ -124,7 +124,14 @@ up.link = (($) ->
124
124
  The selector to replace.
125
125
  Defaults to the `[up-target]`, `[up-modal]` or `[up-popup]` attribute on `link`.
126
126
  If no target is given, the `<body>` element will be replaced.
127
+ @param {boolean|string} [options.reveal=true]
128
+ Whether to [reveal](/up.reveal) the target fragment after it was replaced.
127
129
 
130
+ You can also pass a CSS selector for the element to reveal.
131
+ @param {boolean|string} [options.failReveal=true]
132
+ Whether to [reveal](/up.reveal) the target fragment when the server responds with an error.
133
+
134
+ You can also pass a CSS selector for the element to reveal.
128
135
  @return {Promise}
129
136
  A promise that will be fulfilled when the link destination
130
137
  has been loaded and rendered.
@@ -163,6 +170,7 @@ up.link = (($) ->
163
170
  options.failTransition = u.option(options.failTransition, u.castedAttr($link, 'up-fail-transition'), 'none')
164
171
  options.history = u.option(options.history, u.castedAttr($link, 'up-history'))
165
172
  options.reveal = u.option(options.reveal, u.castedAttr($link, 'up-reveal'), true)
173
+ options.failReveal = u.option(options.failReveal, u.castedAttr($link, 'up-fail-reveal'), true)
166
174
  options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'))
167
175
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'))
168
176
  options.method = followMethod($link, options)
@@ -347,10 +355,10 @@ up.link = (($) ->
347
355
  @param {string} [up-transition='none']
348
356
  The [transition](/up.motion) to use for morphing between the old and new elements.
349
357
  @param [up-fail-target='body']
350
- The selector to replace if the server responds with a non-200 status code.
358
+ The selector to replace if the server responds with an error.
351
359
  @param {string} [up-fail-transition='none']
352
360
  The [transition](/up.motion) to use for morphing between the old and new elements
353
- when the server responds with a non-200 status code.
361
+ when the server responds with an error.
354
362
  @param {string} [up-fallback]
355
363
  The selector to update when the original target was not found in the page.
356
364
  @param {string} [up-href]
@@ -360,7 +368,13 @@ up.link = (($) ->
360
368
  A message that will be displayed in a cancelable confirmation dialog
361
369
  before the link is followed.
362
370
  @param {string} [up-reveal='true']
363
- Whether to reveal the target element within its viewport before updating.
371
+ Whether to reveal the target element after it was replaced.
372
+
373
+ You can also pass a CSS selector for the element to reveal.
374
+ @param {string} [up-fail-reveal='true']
375
+ Whether to reveal the target element when the server responds with an error.
376
+
377
+ You can also pass a CSS selector for the element to reveal.
364
378
  @param {string} [up-restore-scroll='false']
365
379
  Whether to restore previously known scroll position of all viewports
366
380
  within the target selector.
@@ -416,14 +430,14 @@ up.link = (($) ->
416
430
  @param {string} [up-method='get']
417
431
  The HTTP method to use for the request.
418
432
  @param [up-fail-target='body']
419
- The selector to replace if the server responds with a non-200 status code.
433
+ The selector to replace if the server responds with an error.
420
434
  @param {string} [up-fallback]
421
435
  The selector to update when the original target was not found in the page.
422
436
  @param {string} [up-transition='none']
423
437
  The [transition](/up.motion) to use for morphing between the old and new elements.
424
438
  @param {string} [up-fail-transition='none']
425
439
  The [transition](/up.motion) to use for morphing between the old and new elements
426
- when the server responds with a non-200 status code.
440
+ when the server responds with an error.
427
441
  @param [up-href]
428
442
  The destination URL to follow.
429
443
  If omitted, the the link's `href` attribute will be used.
@@ -11,16 +11,22 @@ That said, there is an **optional** protocol your server can use to
11
11
  exchange additional information when Unpoly is [updating fragments](/up.link).
12
12
 
13
13
  While the protocol can help you optimize performance and handle some
14
- edge cases, implementing it is entirely optional. For instance,
14
+ edge cases, implementing it is **entirely optional**. For instance,
15
15
  `unpoly.com` itself is a static site that uses Unpoly on the frontend
16
16
  and doesn't even have a server component.
17
17
 
18
- If you have [installed Unpoly as a Rails gem](/install/rails), the protocol
19
- is already implemented and you will get some
20
- [Ruby bindings](https://github.com/unpoly/unpoly/blob/master/README_RAILS.md)
21
- in your controllers and views. If your server-side app uses another language
22
- or framework, you should be able to implement the protocol in a very short time.
18
+ ## Existing implementations
23
19
 
20
+ You should be able to implement the protocol in a very short time.
21
+ There are existing implementations for various web frameworks:
22
+
23
+ - [Ruby on Rails](/install/rails)
24
+ - [Roda](https://github.com/adam12/roda-unpoly)
25
+ - [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino, Hanami, Cuba, ...)
26
+ - [Phoenix](https://elixirforum.com/t/unpoly-a-framework-like-turbolinks/3614/15) (Elixir)
27
+
28
+
29
+ ## Protocol details
24
30
 
25
31
  \#\#\# Redirect detection for IE11
26
32
 
@@ -0,0 +1,63 @@
1
+ ###*
2
+ Passive updates
3
+ ===============
4
+
5
+ This work-in-progress package will contain functionality to
6
+ passively receive updates from the server.
7
+
8
+ @class up.radio
9
+ ###
10
+ up.radio = (($) ->
11
+
12
+ u = up.util
13
+
14
+ ###*
15
+ Configures defaults for passive updates.
16
+
17
+ @property up.radio.config
18
+ @param {Array<string>} [options.hungry]
19
+ An array of CSS selectors that is replaced whenever a matching element is found in a response.
20
+ These elements are replaced even when they were not targeted directly.
21
+
22
+ By default this contains the [`[up-hungry]`](/up-hungry) attribute as well as
23
+ `<meta name="csrf-param">` and `<meta name="csrf-token">` tags.
24
+ @param {string} [options.hungryTransition=null]
25
+ The transition to use when a [hungry element](/up-hungry) is replacing itself
26
+ while another target is replaced.
27
+
28
+ By default this is not set and the original replacement's transition is used.
29
+ @stable
30
+ ###
31
+ config = u.config
32
+ hungry: ['[up-hungry]', 'meta[name="csrf-param"]', 'meta[name="csrf-token"]']
33
+ hungryTransition: null
34
+
35
+ reset = ->
36
+ config.reset()
37
+
38
+ ###*
39
+ @function up.radio.hungrySelector
40
+ @internal
41
+ ###
42
+ hungrySelector = ->
43
+ u.multiSelector(config.hungry)
44
+
45
+ ###*
46
+ Elements with this attribute are [updated](/up.replace) whenever there is a
47
+ matching element found in a successful response. The element is replaced even
48
+ when it isn't [targeted](/a-up-target) directly.
49
+
50
+ Use cases for this are unread message counters or notification flashes.
51
+ Such elements often live in the layout, outside of the content area that is
52
+ being replaced.
53
+
54
+ @selector [up-hungry]
55
+ @stable
56
+ ###
57
+
58
+ up.on 'up:framework:reset', reset
59
+
60
+ config: config
61
+ hungrySelector: hungrySelector
62
+
63
+ )(jQuery)
@@ -187,20 +187,31 @@ up.util = (($) ->
187
187
  $element = $(element)
188
188
  selector = undefined
189
189
 
190
+ tagName = $element.prop('tagName').toLowerCase()
191
+
190
192
  if upId = presence($element.attr("up-id"))
191
- selector = "[up-id='#{upId}']"
193
+ selector = attributeSelector('up-id', upId)
192
194
  else if id = presence($element.attr("id"))
193
- selector = "##{id}"
195
+ if id.match(/^[a-z0-9\-_]+$/i)
196
+ selector = "##{id}"
197
+ else
198
+ selector = attributeSelector('id', id)
194
199
  else if name = presence($element.attr("name"))
195
- selector = "[name='#{name}']"
200
+ selector = tagName + attributeSelector('name', name)
196
201
  else if classes = presence(nonUpClasses($element))
197
202
  selector = ''
198
203
  for klass in classes
199
204
  selector += ".#{klass}"
205
+ else if ariaLabel = presence($element.attr("aria-label"))
206
+ selector = attributeSelector('aria-label', ariaLabel)
200
207
  else
201
- selector = $element.prop('tagName').toLowerCase()
208
+ selector = tagName
202
209
  selector
203
210
 
211
+ attributeSelector = (attribute, value) ->
212
+ value = value.replace(/"/g, '\\"')
213
+ "[#{attribute}=\"#{value}\"]"
214
+
204
215
  nonUpClasses = ($element) ->
205
216
  classString = $element.attr('class') || ''
206
217
  classes = classString.split(' ')
@@ -18,6 +18,7 @@
18
18
  #= require ./unpoly/modal
19
19
  #= require ./unpoly/tooltip
20
20
  #= require ./unpoly/feedback
21
+ #= require ./unpoly/radio
21
22
  #= require ./unpoly/rails
22
23
 
23
24
  up.boot()
@@ -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.52.0'
7
+ VERSION = '0.53.0'
8
8
  end
9
9
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unpoly",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "Unobtrusive JavaScript framework",
5
5
  "main": "dist/unpoly.js",
6
6
  "files": [
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- unpoly-rails (0.51.1)
4
+ unpoly-rails (0.52.0)
5
5
  rails (>= 3)
6
6
 
7
7
  GEM
@@ -0,0 +1,5 @@
1
+ class HashTestController < ApplicationController
2
+
3
+ layout 'integration_test'
4
+
5
+ end
@@ -0,0 +1,13 @@
1
+ <h1>Hash links (without Unpoly)</h1>
2
+
3
+ <div class="example" id="one" style="height: 1000px">
4
+ <h2>This is #one</h2>
5
+
6
+ <a href="#two">Scroll down to #two</a>
7
+ </div>
8
+
9
+ <div class="example" id="two" style="height: 1000px">
10
+ <h2>This is #two</h2>
11
+
12
+ <a href="#one">Scroll up to #one</a>
13
+ </div>
@@ -60,6 +60,7 @@
60
60
  <li><%= link_to 'Error', '/error_test/trigger' %></li>
61
61
  <li><%= link_to 'Booting with non-GET method', '/method_test/page1' %></li>
62
62
  <li><%= link_to 'Fragment update', '/replace_test/page1' %></li>
63
+ <li><%= link_to 'Hash links (without Unpoly)', '/hash_test/vanilla' %></li>
63
64
  </ul>
64
65
 
65
66
  </div>
@@ -11,6 +11,7 @@ Rails.application.routes.draw do
11
11
  get 'error_test/:action', controller: 'error_test'
12
12
  post 'error_test/:action', controller: 'error_test'
13
13
  get 'replace_test/:action', controller: 'replace_test'
14
+ get 'hash_test/:action', controller: 'hash_test'
14
15
 
15
16
  namespace :form_test do
16
17
  resource :basic, only: [:new, :create]
@@ -133,7 +133,7 @@ describe 'up.dom', ->
133
133
  up.replace('.middle', '/path', method: 'put')
134
134
  next => expect(@lastRequest()).toHaveRequestMethod('PUT')
135
135
 
136
- describe 'when the server responds with a non-200 status code', ->
136
+ describe 'when the server responds with an error', ->
137
137
 
138
138
  it 'replaces the first fallback instead of the given selector', asyncSpec (next) ->
139
139
  up.dom.config.fallbacks = ['.fallback']
@@ -392,6 +392,116 @@ describe 'up.form', ->
392
392
  next => @respondWith('<div class="response">new-text</div>')
393
393
  next =>expect(up.browser.url()).toEqual(@hrefBeforeExample)
394
394
 
395
+ describe 'revealing', ->
396
+
397
+ it 'reaveals the target fragment if the submission succeeds', asyncSpec (next) ->
398
+ $form = affix('form[action="/action"][up-target=".target"]')
399
+ $target = affix('.target')
400
+
401
+ revealStub = up.layout.knife.mock('reveal')
402
+
403
+ up.submit($form)
404
+
405
+ next =>
406
+ @respondWith('<div class="target">new text</div>')
407
+
408
+ next =>
409
+ expect(revealStub).toHaveBeenCalled()
410
+ expect(revealStub.calls.mostRecent().args[0]).toBeMatchedBy('.target')
411
+
412
+ it 'reveals the form if the submission fails', asyncSpec (next) ->
413
+ $form = affix('form#foo-form[action="/action"][up-target=".target"]')
414
+ $target = affix('.target')
415
+
416
+ revealStub = up.layout.knife.mock('reveal')
417
+
418
+ up.submit($form)
419
+
420
+ next =>
421
+ @respondWith
422
+ status: 500,
423
+ responseText: """
424
+ <form id="foo-form">
425
+ Errors here
426
+ </form>
427
+ """
428
+
429
+ next =>
430
+ expect(revealStub).toHaveBeenCalled()
431
+ expect(revealStub.calls.mostRecent().args[0]).toBeMatchedBy('#foo-form')
432
+
433
+
434
+ describe 'with { reveal } option', ->
435
+
436
+ it 'allows to reveal a different selector', asyncSpec (next) ->
437
+ $form = affix('form[action="/action"][up-target=".target"]')
438
+ $target = affix('.target')
439
+ $other = affix('.other')
440
+
441
+ revealStub = up.layout.knife.mock('reveal')
442
+
443
+ up.submit($form, reveal: '.other')
444
+
445
+ next =>
446
+ @respondWith """
447
+ <div class="target">
448
+ new text
449
+ </div>
450
+ <div class="other">
451
+ new other
452
+ </div>
453
+ """
454
+
455
+ next =>
456
+ expect(revealStub).toHaveBeenCalled()
457
+ expect(revealStub.calls.mostRecent().args[0]).toBeMatchedBy('.other')
458
+
459
+ it 'still reveals the form for a failed submission', asyncSpec (next) ->
460
+ $form = affix('form#foo-form[action="/action"][up-target=".target"]')
461
+ $target = affix('.target')
462
+ $other = affix('.other')
463
+
464
+ revealStub = up.layout.knife.mock('reveal')
465
+
466
+ up.submit($form, reveal: '.other')
467
+
468
+ next =>
469
+ @respondWith
470
+ status: 500,
471
+ responseText: """
472
+ <form id="foo-form">
473
+ Errors here
474
+ </form>
475
+ """
476
+
477
+ next =>
478
+ expect(revealStub).toHaveBeenCalled()
479
+ expect(revealStub.calls.mostRecent().args[0]).toBeMatchedBy('#foo-form')
480
+
481
+ describe 'with { failReveal } option', ->
482
+
483
+ it 'reveals the given selector for a failed submission', asyncSpec (next) ->
484
+ $form = affix('form#foo-form[action="/action"][up-target=".target"]')
485
+ $target = affix('.target')
486
+ $other = affix('.other')
487
+
488
+ revealStub = up.layout.knife.mock('reveal')
489
+
490
+ up.submit($form, reveal: '.other', failReveal: '.error')
491
+
492
+ next =>
493
+ @respondWith
494
+ status: 500,
495
+ responseText: """
496
+ <form id="foo-form">
497
+ <div class="error">Errors here</div>
498
+ </form>
499
+ """
500
+
501
+ next =>
502
+ expect(revealStub).toHaveBeenCalled()
503
+ expect(revealStub.calls.mostRecent().args[0]).toBeMatchedBy('.error')
504
+
395
505
  describe 'in a form with file inputs', ->
396
506
 
397
507
  beforeEach ->
@@ -599,7 +709,7 @@ describe 'up.form', ->
599
709
  next =>
600
710
  request = @lastRequest()
601
711
  expect(request.requestHeaders['X-Up-Validate']).toEqual('user')
602
- expect(request.requestHeaders['X-Up-Target']).toEqual(".field-group:has([name='user'])")
712
+ expect(request.requestHeaders['X-Up-Target']).toEqual('.field-group:has(input[name="user"])')
603
713
 
604
714
  @respondWith """
605
715
  <div class="field-group has-error">