upjs-rails 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/dist/up.js +929 -374
  4. data/dist/up.min.js +2 -2
  5. data/lib/assets/javascripts/up/browser.js.coffee +31 -14
  6. data/lib/assets/javascripts/up/bus.js.coffee +87 -22
  7. data/lib/assets/javascripts/up/flow.js.coffee +119 -43
  8. data/lib/assets/javascripts/up/form.js.coffee +188 -57
  9. data/lib/assets/javascripts/up/link.js.coffee +57 -21
  10. data/lib/assets/javascripts/up/modal.js.coffee +77 -63
  11. data/lib/assets/javascripts/up/motion.js.coffee +10 -9
  12. data/lib/assets/javascripts/up/popup.js.coffee +54 -40
  13. data/lib/assets/javascripts/up/proxy.js.coffee +46 -17
  14. data/lib/assets/javascripts/up/rails.js.coffee +22 -4
  15. data/lib/assets/javascripts/up/syntax.js.coffee +2 -2
  16. data/lib/assets/javascripts/up/util.js.coffee +100 -16
  17. data/lib/upjs/rails/inspector.rb +3 -3
  18. data/lib/upjs/rails/version.rb +1 -1
  19. data/spec_app/Gemfile.lock +1 -4
  20. data/spec_app/app/controllers/test_controller.rb +2 -2
  21. data/spec_app/spec/controllers/test_controller_spec.rb +5 -5
  22. data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +9 -0
  23. data/spec_app/spec/javascripts/helpers/knife.js.coffee +0 -1
  24. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +4 -5
  25. data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +5 -0
  26. data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +8 -0
  27. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +26 -0
  28. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +203 -91
  29. data/spec_app/spec/javascripts/up/form_spec.js.coffee +244 -49
  30. data/spec_app/spec/javascripts/up/history_spec.js.coffee +8 -2
  31. data/spec_app/spec/javascripts/up/link_spec.js.coffee +83 -30
  32. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -17
  33. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +4 -4
  34. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +1 -1
  35. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +26 -16
  36. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +45 -13
  37. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +48 -0
  38. data/spec_app/spec/javascripts/up/util_spec.js.coffee +48 -0
  39. metadata +5 -2
@@ -0,0 +1,9 @@
1
+ window.describeCapability = (capability, examples) ->
2
+ if up.browser[capability]()
3
+ examples()
4
+
5
+ window.describeFallback = (capability, examples) ->
6
+ describe "in a browser without #{capability}", ->
7
+ beforeEach ->
8
+ spyOn(up.browser, capability).and.returnValue(false)
9
+ examples()
@@ -51,7 +51,6 @@
51
51
  mock: mock
52
52
 
53
53
  reset = ->
54
- console.log("Reset with cleaners %o", Knife.cleaners)
55
54
  cleaner() while cleaner = Knife.cleaners.pop()
56
55
 
57
56
  me = {}
@@ -1,9 +1,8 @@
1
1
  beforeEach ->
2
- @previousHref = location.href
3
- @previousTitle = document.title
2
+ @hrefBeforeExample = location.href
3
+ @titleBeforeExample = document.title
4
4
 
5
5
  afterEach ->
6
6
  if up.browser.canPushState()
7
- history.replaceState?({}, @previousTitle, @previousHref)
8
- document.title = @previousTitle
9
-
7
+ history.replaceState?({}, @titleBeforeExample, @hrefBeforeExample)
8
+ document.title = @titleBeforeExample
@@ -0,0 +1,5 @@
1
+ beforeEach ->
2
+ jasmine.addMatchers
3
+ toBePresent: (util, customEqualityTesters) ->
4
+ compare: (actual) ->
5
+ pass: up.util.isPresent(actual)
@@ -0,0 +1,8 @@
1
+ beforeEach ->
2
+ jasmine.addMatchers
3
+ toHaveRequestMethod: (util, customEqualityTesters) ->
4
+ compare: (request, expectedMethod) ->
5
+ console.log("real is %o, wrapped is %o", request.method, request.data()['_method'])
6
+ realMethodMatches = (request.method == expectedMethod)
7
+ wrappedMethodMatches = util.equals(request.data()['_method'], [expectedMethod], customEqualityTesters)
8
+ pass: realMethodMatches || wrappedMethodMatches
@@ -47,6 +47,32 @@ describe 'up.bus', ->
47
47
  $('.child').click()
48
48
  expect(observeArgs).toHaveBeenCalledWith('child', {})
49
49
 
50
+ describe 'up.off', ->
51
+
52
+ it 'unregisters an event listener previously registered through up.on', ->
53
+ $child = affix('.child')
54
+ clickSpy = jasmine.createSpy()
55
+ up.on 'click', '.child', clickSpy
56
+ $('.child').click()
57
+ up.off 'click', '.child', clickSpy
58
+ $('.child').click()
59
+ expect(clickSpy.calls.count()).toEqual(1)
60
+
61
+ it 'throws an error if the given event listener was not registered through up.on', ->
62
+ someFunction = ->
63
+ offing = -> up.off 'click', '.child', someFunction
64
+ expect(offing).toThrowError(/(not|never) registered/i)
65
+
66
+ it 'reduces the internally tracked list of event listeners (bugfix for memory leak)', ->
67
+ getCount = -> up.bus.knife.get('Object.keys(liveUpDescriptions).length')
68
+ oldCount = getCount()
69
+ expect(oldCount).toBeGreaterThan(0)
70
+ clickSpy = jasmine.createSpy()
71
+ up.on 'click', '.child', clickSpy
72
+ expect(getCount()).toBe(oldCount + 1)
73
+ up.off 'click', '.child', clickSpy
74
+ expect(getCount()).toBe(oldCount)
75
+
50
76
  describe 'up.emit', ->
51
77
 
52
78
  it 'triggers an event on the document', ->
@@ -6,7 +6,7 @@ describe 'up.flow', ->
6
6
 
7
7
  describe 'up.replace', ->
8
8
 
9
- if up.browser.canPushState()
9
+ describeCapability 'canPushState', ->
10
10
 
11
11
  beforeEach ->
12
12
 
@@ -21,7 +21,7 @@ describe 'up.flow', ->
21
21
  <div class="after">new-after</div>
22
22
  """
23
23
 
24
- @respond = -> @respondWith(@responseText)
24
+ @respond = (options = {}) -> @respondWith(@responseText, options)
25
25
 
26
26
  it 'replaces the given selector with the same selector from a freshly fetched page', (done) ->
27
27
  promise = up.replace('.middle', '/path')
@@ -31,20 +31,133 @@ describe 'up.flow', ->
31
31
  expect($('.middle')).toHaveText('new-middle')
32
32
  expect($('.after')).toHaveText('old-after')
33
33
  done()
34
-
35
- it 'should set the browser location to the given URL', (done) ->
36
- promise = up.replace('.middle', '/path')
37
- @respond()
38
- promise.then ->
39
- expect(window.location.pathname).toBe('/path')
40
- done()
41
34
 
42
- it "detects a redirect's new URL when the server sets an X-Up-Location header", (done) ->
43
- promise = up.replace('.middle', '/path')
44
- @respondWith(@responseText, responseHeaders: { 'X-Up-Location': '/other-path' })
45
- promise.then ->
46
- expect(window.location.pathname).toBe('/other-path')
47
- done()
35
+ it 'sends an X-Up-Target HTTP headers along with the request', ->
36
+ up.replace('.middle', '/path')
37
+ request = @lastRequest()
38
+ console.log(request.requestHeaders)
39
+ expect(request.requestHeaders['X-Up-Target']).toEqual('.middle')
40
+
41
+ describe 'with { data } option', ->
42
+
43
+ it "uses the given params as a non-GET request's payload", ->
44
+ givenParams = { 'foo-key': 'foo-value', 'bar-key': 'bar-value' }
45
+ up.replace('.middle', '/path', method: 'put', data: givenParams)
46
+ expect(@lastRequest().data()['foo-key']).toEqual(['foo-value'])
47
+ expect(@lastRequest().data()['bar-key']).toEqual(['bar-value'])
48
+
49
+ it "encodes the given params into the URL of a GET request", ->
50
+ givenParams = { 'foo-key': 'foo value', 'bar-key': 'bar value' }
51
+ up.replace('.middle', '/path', method: 'get', data: givenParams)
52
+ expect(@lastRequest().url).toEndWith('/path?foo-key=foo+value&bar-key=bar+value')
53
+
54
+ it 'uses a HTTP method given as { method } option', ->
55
+ up.replace('.middle', '/path', method: 'put')
56
+ expect(@lastRequest()).toHaveRequestMethod('PUT')
57
+
58
+ describe 'if the server responds with a non-200 status code', ->
59
+
60
+ it 'replaces the <body> instead of the given selector', ->
61
+ implantSpy = up.flow.knife.mock('implant') # can't have the example replace the Jasmine test runner UI
62
+ up.replace('.middle', '/path')
63
+ @respond(status: 500)
64
+ expect(implantSpy).toHaveBeenCalledWith('body', jasmine.any(String), jasmine.any(Object))
65
+
66
+ it 'uses a target selector given as { failTarget } option', ->
67
+ up.replace('.middle', '/path', failTarget: '.after')
68
+ @respond(status: 500)
69
+ expect($('.middle')).toHaveText('old-middle')
70
+ expect($('.after')).toHaveText('new-after')
71
+
72
+ describe 'history', ->
73
+
74
+ it 'should set the browser location to the given URL', (done) ->
75
+ promise = up.replace('.middle', '/path')
76
+ @respond()
77
+ promise.then ->
78
+ expect(location.href).toEndWith('/path')
79
+ done()
80
+
81
+ it 'does not add a history entry after non-GET requests', ->
82
+ promise = up.replace('.middle', '/path', method: 'post')
83
+ @respond()
84
+ expect(location.href).toEndWith(@hrefBeforeExample)
85
+
86
+ it 'adds a history entry after non-GET requests if the response includes a { X-Up-Method: "get" } header (will happen after a redirect)', ->
87
+ promise = up.replace('.middle', '/path', method: 'post')
88
+ @respond(responseHeaders: { 'X-Up-Method': 'get' })
89
+ expect(location.href).toEndWith('/path')
90
+
91
+ it 'does not a history entry after a failed GET-request', ->
92
+ promise = up.replace('.middle', '/path', method: 'post', failTarget: '.middle')
93
+ @respond(status: 500)
94
+ expect(location.href).toEndWith(@hrefBeforeExample)
95
+
96
+ it 'does not add a history entry with { history: false } option', ->
97
+ promise = up.replace('.middle', '/path', history: false)
98
+ @respond()
99
+ expect(location.href).toEndWith(@hrefBeforeExample)
100
+
101
+ it "detects a redirect's new URL when the server sets an X-Up-Location header", ->
102
+ promise = up.replace('.middle', '/path')
103
+ @respond(responseHeaders: { 'X-Up-Location': '/other-path' })
104
+ expect(location.href).toEndWith('/other-path')
105
+
106
+ it 'adds params from a { data } option to the URL of a GET request', ->
107
+ promise = up.replace('.middle', '/path', data: { 'foo-key': 'foo value', 'bar-key': 'bar value' })
108
+ @respond()
109
+ console.log("EXPECTATION COMING UP AGAINST %o", location.pathname)
110
+ expect(location.href).toEndWith('/path?foo-key=foo%20value&bar-key=bar%20value')
111
+
112
+ describe 'if a URL is given as { history } option', ->
113
+
114
+ it 'uses that URL as the new location after a GET request', ->
115
+ promise = up.replace('.middle', '/path', history: '/given-path')
116
+ @respond(failTarget: '.middle')
117
+ expect(location.href).toEndWith('/given-path')
118
+
119
+ it 'adds a history entry after a non-GET request', ->
120
+ promise = up.replace('.middle', '/path', method: 'post', history: '/given-path')
121
+ @respond(failTarget: '.middle')
122
+ expect(location.href).toEndWith('/given-path')
123
+
124
+ it 'does not add a history entry after a failed non-GET request', ->
125
+ promise = up.replace('.middle', '/path', method: 'post', history: '/given-path', failTarget: '.middle')
126
+ @respond(failTarget: '.middle', status: 500)
127
+ expect(location.href).toEndWith(@hrefBeforeExample)
128
+
129
+ describe 'source', ->
130
+
131
+ it 'remembers the source the fragment was retrieved from', (done) ->
132
+ promise = up.replace('.middle', '/path')
133
+ @respond()
134
+ promise.then ->
135
+ expect($('.middle').attr('up-source')).toMatch(/\/path$/)
136
+ done()
137
+
138
+ it 'reuses the previous source for a non-GET request (since that is reloadable)', ->
139
+ @oldMiddle.attr('up-source', '/previous-source')
140
+ up.replace('.middle', '/path', method: 'post')
141
+ @respond()
142
+ expect($('.middle')).toHaveText('new-middle')
143
+ expect(up.flow.source('.middle')).toEndWith('/previous-source')
144
+
145
+ describe 'if a URL is given as { source } option', ->
146
+
147
+ it 'uses that URL as the source for a GET request', ->
148
+ promise = up.replace('.middle', '/path', source: '/given-path')
149
+ @respond()
150
+ expect(up.flow.source('.middle')).toEndWith('/given-path')
151
+
152
+ it 'uses that URL as the source after a non-GET request', ->
153
+ promise = up.replace('.middle', '/path', method: 'post', source: '/given-path')
154
+ @respond()
155
+ expect(up.flow.source('.middle')).toEndWith('/given-path')
156
+
157
+ it 'still reuses the previous URL after a failed non-GET request', ->
158
+ promise = up.replace('.middle', '/path', method: 'post', source: '/given-path', failTarget: '.middle')
159
+ @respond(status: 500)
160
+ expect(up.flow.source('.middle')).toEndWith(@hrefBeforeExample)
48
161
 
49
162
  it 'understands non-standard CSS selector extensions such as :has(...)', (done) ->
50
163
  $first = affix('.boxx#first')
@@ -64,82 +177,79 @@ describe 'up.flow', ->
64
177
  expect($('#second span')).toHaveText('old second')
65
178
  done()
66
179
 
67
- it 'marks the element with the URL from which it was retrieved', (done) ->
68
- promise = up.replace('.middle', '/path')
69
- @respond()
70
- promise.then ->
71
- expect($('.middle').attr('up-source')).toMatch(/\/path$/)
72
- done()
73
-
74
- it 'replaces multiple selectors separated with a comma', (done) ->
75
- promise = up.replace('.middle, .after', '/path')
76
- @respond()
77
- promise.then ->
78
- expect($('.before')).toHaveText('old-before')
79
- expect($('.middle')).toHaveText('new-middle')
80
- expect($('.after')).toHaveText('new-after')
81
- done()
82
-
83
- it 'replaces the body if asked to replace the "html" selector'
84
-
85
- it "sets the document title to a 'title' tag in the response", ->
86
- affix('.container').text('old container text')
87
- up.replace('.container', '/path')
88
- @respondWith """
89
- <html>
90
- <head>
91
- <title>Title from HTML</title>
92
- </head>
93
- <body>
180
+ describe 'document title', ->
181
+
182
+ it "sets the document title to a 'title' tag in the response", ->
183
+ affix('.container').text('old container text')
184
+ up.replace('.container', '/path')
185
+ @respondWith """
186
+ <html>
187
+ <head>
188
+ <title>Title from HTML</title>
189
+ </head>
190
+ <body>
191
+ <div class='container'>
192
+ new container text
193
+ </div>
194
+ </body>
195
+ </html>
196
+ """
197
+ expect($('.container')).toHaveText('new container text')
198
+ expect(document.title).toBe('Title from HTML')
199
+
200
+ it "sets the document title to an 'X-Up-Title' header in the response", ->
201
+ affix('.container').text('old container text')
202
+ up.replace('.container', '/path')
203
+ @respondWith
204
+ responseHeaders:
205
+ 'X-Up-Title': 'Title from header'
206
+ responseText: """
94
207
  <div class='container'>
95
208
  new container text
96
209
  </div>
97
- </body>
98
- </html>
99
- """
100
- expect($('.container')).toHaveText('new container text')
101
- expect(document.title).toBe('Title from HTML')
102
-
103
- it "sets the document title to an 'X-Up-Title' header in the response", ->
104
- affix('.container').text('old container text')
105
- up.replace('.container', '/path')
106
- @respondWith
107
- responseHeaders:
108
- 'X-Up-Title': 'Title from header'
109
- responseText: """
110
- <div class='container'>
111
- new container text
112
- </div>
113
- """
114
- expect($('.container')).toHaveText('new container text')
115
- expect(document.title).toBe('Title from header')
116
-
117
- it 'prepends instead of replacing when the target has a :before pseudo-selector', (done) ->
118
- promise = up.replace('.middle:before', '/path')
119
- @respond()
120
- promise.then ->
121
- expect($('.before')).toHaveText('old-before')
122
- expect($('.middle')).toHaveText('new-middleold-middle')
123
- expect($('.after')).toHaveText('old-after')
124
- done()
210
+ """
211
+ expect($('.container')).toHaveText('new container text')
212
+ expect(document.title).toBe('Title from header')
125
213
 
126
- it 'appends instead of replacing when the target has a :after pseudo-selector', (done) ->
127
- promise = up.replace('.middle:after', '/path')
128
- @respond()
129
- promise.then ->
130
- expect($('.before')).toHaveText('old-before')
131
- expect($('.middle')).toHaveText('old-middlenew-middle')
132
- expect($('.after')).toHaveText('old-after')
133
- done()
214
+ describe 'selector processing', ->
134
215
 
135
- it "lets the developer choose between replacing/prepending/appending for each selector", (done) ->
136
- promise = up.replace('.before:before, .middle, .after:after', '/path')
137
- @respond()
138
- promise.then ->
139
- expect($('.before')).toHaveText('new-beforeold-before')
140
- expect($('.middle')).toHaveText('new-middle')
141
- expect($('.after')).toHaveText('old-afternew-after')
142
- done()
216
+ it 'replaces multiple selectors separated with a comma', (done) ->
217
+ promise = up.replace('.middle, .after', '/path')
218
+ @respond()
219
+ promise.then ->
220
+ expect($('.before')).toHaveText('old-before')
221
+ expect($('.middle')).toHaveText('new-middle')
222
+ expect($('.after')).toHaveText('new-after')
223
+ done()
224
+
225
+ it 'replaces the body if asked to replace the "html" selector'
226
+
227
+ it 'prepends instead of replacing when the target has a :before pseudo-selector', (done) ->
228
+ promise = up.replace('.middle:before', '/path')
229
+ @respond()
230
+ promise.then ->
231
+ expect($('.before')).toHaveText('old-before')
232
+ expect($('.middle')).toHaveText('new-middleold-middle')
233
+ expect($('.after')).toHaveText('old-after')
234
+ done()
235
+
236
+ it 'appends instead of replacing when the target has a :after pseudo-selector', (done) ->
237
+ promise = up.replace('.middle:after', '/path')
238
+ @respond()
239
+ promise.then ->
240
+ expect($('.before')).toHaveText('old-before')
241
+ expect($('.middle')).toHaveText('old-middlenew-middle')
242
+ expect($('.after')).toHaveText('old-after')
243
+ done()
244
+
245
+ it "lets the developer choose between replacing/prepending/appending for each selector", (done) ->
246
+ promise = up.replace('.before:before, .middle, .after:after', '/path')
247
+ @respond()
248
+ promise.then ->
249
+ expect($('.before')).toHaveText('new-beforeold-before')
250
+ expect($('.middle')).toHaveText('new-middle')
251
+ expect($('.after')).toHaveText('old-afternew-after')
252
+ done()
143
253
 
144
254
  it 'executes only those script-tags in the response that get inserted into the DOM', (done) ->
145
255
  window.scriptTagExecuted = jasmine.createSpy('scriptTagExecuted')
@@ -272,7 +382,9 @@ describe 'up.flow', ->
272
382
  expect($('.up-insertion')).not.toExist()
273
383
  done()
274
384
 
275
- else
385
+ it 'uses a { failTransition } option if the request failed'
386
+
387
+ describeFallback 'canPushState', ->
276
388
 
277
389
  it 'makes a full page load', ->
278
390
  spyOn(up.browser, 'loadPage')
@@ -360,8 +472,8 @@ describe 'up.flow', ->
360
472
  expect(destructor).toHaveBeenCalled()
361
473
 
362
474
  describe 'up.reload', ->
363
-
364
- if up.browser.canPushState()
475
+
476
+ describeCapability 'canPushState', ->
365
477
 
366
478
  it 'reloads the given selector from the closest known source URL', (done) ->
367
479
  affix('.container[up-source="/source"] .element').find('.element').text('old text')
@@ -377,8 +489,8 @@ describe 'up.flow', ->
377
489
  <div class="element">new text</div>
378
490
  </div>
379
491
  """
380
-
381
- else
492
+
493
+ describeFallback 'canPushState', ->
382
494
 
383
495
  it 'makes a page load from the closest known source URL', ->
384
496
  affix('.container[up-source="/source"] .element').find('.element').text('old text')
@@ -7,13 +7,13 @@ describe 'up.form', ->
7
7
  describe 'up.observe', ->
8
8
 
9
9
  changeEvents = if up.browser.canInputEvent()
10
- # Actually we only need `input`, but we want to notice
11
- # if another script manually triggers `change` on the element.
10
+ # Actually we only need `input`, but we want to notice
11
+ # if another script manually triggers `change` on the element.
12
12
  ['input', 'change']
13
13
  else
14
- # Actually we won't ever get `input` from the user in this browser,
15
- # but we want to notice if another script manually triggers `input`
16
- # on the element.
14
+ # Actually we won't ever get `input` from the user in this browser,
15
+ # but we want to notice if another script manually triggers `input`
16
+ # on the element.
17
17
  ['input', 'change', 'keypress', 'paste', 'cut', 'click', 'propertychange']
18
18
 
19
19
  u.each changeEvents, (eventName) ->
@@ -83,27 +83,24 @@ describe 'up.form', ->
83
83
  done()
84
84
 
85
85
  describe 'up.submit', ->
86
-
87
- if up.browser.canPushState()
86
+
87
+ describeCapability 'canPushState', ->
88
88
 
89
89
  beforeEach ->
90
- $form = affix('form[action="/path/to"][method="put"][up-target=".response"]')
91
- $form.append('<input name="field1" value="value1">')
92
- $form.append('<input name="field2" value="value2">')
93
-
90
+ @$form = affix('form[action="/path/to"][method="put"][up-target=".response"]')
91
+ @$form.append('<input name="field1" value="value1">')
92
+ @$form.append('<input name="field2" value="value2">')
94
93
  affix('.response').text('old-text')
95
-
96
- @promise = up.submit($form)
97
-
94
+ @promise = up.submit(@$form)
98
95
  @request = @lastRequest()
96
+
97
+ it 'submits the given form and replaces the target with the response', ->
99
98
  expect(@request.url).toMatch /\/path\/to$/
100
- expect(@request.method).toBe 'PUT'
101
- expect(@request.data()).toEqual
102
- field1: ['value1']
103
- field2: ['value2']
104
-
105
- it 'submits the given form and replaces the target with the response', (done) ->
106
-
99
+ expect(@request).toHaveRequestMethod('PUT')
100
+ expect(@request.data()['field1']).toEqual(['value1'])
101
+ expect(@request.data()['field2']).toEqual(['value2'])
102
+ expect(@request.requestHeaders['X-Up-Target']).toEqual('.response')
103
+
107
104
  @respondWith """
108
105
  text-before
109
106
 
@@ -113,15 +110,10 @@ describe 'up.form', ->
113
110
 
114
111
  text-after
115
112
  """
116
-
117
- @promise.then ->
118
- expect($('.response')).toHaveText('new-text')
119
- expect($('body')).not.toHaveText('text-before')
120
- expect($('body')).not.toHaveText('text-after')
121
- done()
122
-
123
- it 'places the response into the form if the submission returns a 5xx status code', (done) ->
124
- @request.respondWith
113
+
114
+ it "places the response into the form and doesn't update the browser URL if the submission returns a 5xx status code", ->
115
+ up.submit(@$form)
116
+ @respondWith
125
117
  status: 500
126
118
  contentType: 'text/html'
127
119
  responseText:
@@ -134,32 +126,47 @@ describe 'up.form', ->
134
126
 
135
127
  text-after
136
128
  """
137
-
138
- @promise.always ->
139
- expect($('.response')).toHaveText('old-text')
140
- expect($('form')).toHaveText('error-messages')
141
- expect($('body')).not.toHaveText('text-before')
142
- expect($('body')).not.toHaveText('text-after')
143
- done()
144
-
145
- it 'respects a X-Up-Location header that the server sends in case of a redirect', (done) ->
146
-
147
- @request.respondWith
129
+ expect(up.browser.url()).toEqual(@hrefBeforeExample)
130
+ expect($('.response')).toHaveText('old-text')
131
+ expect($('form')).toHaveText('error-messages')
132
+ expect($('body')).not.toHaveText('text-before')
133
+ expect($('body')).not.toHaveText('text-after')
134
+
135
+ it 'respects X-Up-Method and X-Up-Location response headers so the server can show that it redirected to a GET URL', ->
136
+ up.submit(@$form)
137
+ @respondWith
148
138
  status: 200
149
139
  contentType: 'text/html'
150
- responseHeaders: { 'X-Up-Location': '/other/path' }
140
+ responseHeaders:
141
+ 'X-Up-Location': '/other-path'
142
+ 'X-Up-Method': 'GET'
151
143
  responseText:
152
144
  """
153
145
  <div class="response">
154
146
  new-text
155
147
  </div>
156
148
  """
157
-
158
- @promise.then ->
159
- expect(up.browser.url()).toMatch(/\/other\/path$/)
160
- done()
161
-
162
- else
149
+
150
+ expect(up.browser.url()).toEndWith('/other-path')
151
+
152
+ describe 'with { history } option', ->
153
+
154
+ it 'uses the given URL as the new browser location if the request succeeded', ->
155
+ up.submit(@$form, history: '/given-path')
156
+ @respondWith('<div class="response">new-text</div>')
157
+ expect(up.browser.url()).toEndWith('/given-path')
158
+
159
+ it 'keeps the current browser location if the request failed', ->
160
+ up.submit(@$form, history: '/given-path', failTarget: '.response')
161
+ @respondWith('<div class="response">new-text</div>', status: 500)
162
+ expect(up.browser.url()).toEqual(@hrefBeforeExample)
163
+
164
+ it 'keeps the current browser location if the option is set to false', ->
165
+ up.submit(@$form, history: false)
166
+ @respondWith('<div class="response">new-text</div>')
167
+ expect(up.browser.url()).toEqual(@hrefBeforeExample)
168
+
169
+ describeFallback 'canPushState', ->
163
170
 
164
171
  it 'submits the given form', ->
165
172
  $form = affix('form[action="/path/to"][method="put"][up-target=".response"]')
@@ -221,7 +228,7 @@ describe 'up.form', ->
221
228
 
222
229
  request = @lastRequest()
223
230
  expect(request.requestHeaders['X-Up-Validate']).toEqual('user')
224
- expect(request.requestHeaders['X-Up-Selector']).toEqual(".field-group:has([name='user'])")
231
+ expect(request.requestHeaders['X-Up-Target']).toEqual(".field-group:has([name='user'])")
225
232
 
226
233
  @respondWith """
227
234
  <div class="field-group has-error">
@@ -274,3 +281,191 @@ describe 'up.form', ->
274
281
  $labels = $('#registration label')
275
282
  expect($labels[0]).not.toHaveText('Validation message')
276
283
  expect($labels[1]).toHaveText('Validation message')
284
+
285
+ describe '[up-toggle]', ->
286
+
287
+ describe 'on a select', ->
288
+
289
+ beforeEach ->
290
+ @$select = affix('select[up-toggle=".target"]')
291
+ @$blankOption = @$select.affix('option').text('<Please select something>').val('')
292
+ @$fooOption = @$select.affix('option[value="foo"]').text('Foo')
293
+ @$barOption = @$select.affix('option[value="bar"]').text('Bar')
294
+ @$bazOption = @$select.affix('option[value="baz"]').text('Baz')
295
+
296
+ it "shows the target element iff its up-show-for attribute contains the select value", ->
297
+ $target = affix('.target[up-show-for="something bar other"]')
298
+ up.hello(@$select)
299
+ expect($target).toBeHidden()
300
+ @$select.val('bar').change()
301
+ expect($target).toBeVisible()
302
+
303
+ it "shows the target element iff its up-hide-for attribute doesn't contain the select value", ->
304
+ $target = affix('.target[up-hide-for="something bar other"]')
305
+ up.hello(@$select)
306
+ expect($target).toBeVisible()
307
+ @$select.val('bar').change()
308
+ expect($target).toBeHidden()
309
+
310
+ it "shows the target element iff it has neither up-show-for nor up-hide-for and the select value is present", ->
311
+ $target = affix('.target')
312
+ up.hello(@$select)
313
+ expect($target).toBeHidden()
314
+ @$select.val('bar').change()
315
+ expect($target).toBeVisible()
316
+
317
+ it "shows the target element iff its up-show-for attribute contains a value ':present' and the select value is present", ->
318
+ $target = affix('.target[up-show-for=":present"]')
319
+ up.hello(@$select)
320
+ expect($target).toBeHidden()
321
+ @$select.val('bar').change()
322
+ expect($target).toBeVisible()
323
+
324
+ it "shows the target element iff its up-show-for attribute contains a value ':blank' and the select value is blank", ->
325
+ $target = affix('.target[up-show-for=":blank"]')
326
+ up.hello(@$select)
327
+ expect($target).toBeVisible()
328
+ @$select.val('bar').change()
329
+ expect($target).toBeHidden()
330
+
331
+ describe 'on a checkbox', ->
332
+
333
+ beforeEach ->
334
+ @$checkbox = affix('input[type="checkbox"][value="1"][up-toggle=".target"]')
335
+
336
+ it "shows the target element iff its up-show-for attribute is :checked and the checkbox is checked", ->
337
+ $target = affix('.target[up-show-for=":checked"]')
338
+ up.hello(@$checkbox)
339
+ expect($target).toBeHidden()
340
+ @$checkbox.prop('checked', true).change()
341
+ expect($target).toBeVisible()
342
+
343
+ it "shows the target element iff its up-show-for attribute is :unchecked and the checkbox is unchecked", ->
344
+ $target = affix('.target[up-show-for=":unchecked"]')
345
+ up.hello(@$checkbox)
346
+ expect($target).toBeVisible()
347
+ @$checkbox.prop('checked', true).change()
348
+ expect($target).toBeHidden()
349
+
350
+ it "shows the target element iff its up-hide-for attribute is :checked and the checkbox is unchecked", ->
351
+ $target = affix('.target[up-hide-for=":checked"]')
352
+ up.hello(@$checkbox)
353
+ expect($target).toBeVisible()
354
+ @$checkbox.prop('checked', true).change()
355
+ expect($target).toBeHidden()
356
+
357
+ it "shows the target element iff its up-hide-for attribute is :unchecked and the checkbox is checked", ->
358
+ $target = affix('.target[up-hide-for=":unchecked"]')
359
+ up.hello(@$checkbox)
360
+ expect($target).toBeHidden()
361
+ @$checkbox.prop('checked', true).change()
362
+ expect($target).toBeVisible()
363
+
364
+ it "shows the target element iff it has neither up-show-for nor up-hide-for and the checkbox is checked", ->
365
+ $target = affix('.target')
366
+ up.hello(@$checkbox)
367
+ expect($target).toBeHidden()
368
+ @$checkbox.prop('checked', true).change()
369
+ expect($target).toBeVisible()
370
+
371
+ describe 'on a group of radio buttons', ->
372
+
373
+ beforeEach ->
374
+ @$buttons = affix('.radio-buttons')
375
+ @$blankButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('')
376
+ @$fooButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('foo')
377
+ @$barButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('bar')
378
+ @$bazkButton = @$buttons.affix('input[type="radio"][name="group"][up-toggle=".target"]').val('baz')
379
+
380
+ it "shows the target element iff its up-show-for attribute contains the selected button value", ->
381
+ $target = affix('.target[up-show-for="something bar other"]')
382
+ up.hello(@$buttons)
383
+ expect($target).toBeHidden()
384
+ @$barButton.prop('checked', true).change()
385
+ expect($target).toBeVisible()
386
+
387
+ it "shows the target element iff its up-hide-for attribute doesn't contain the selected button value", ->
388
+ $target = affix('.target[up-hide-for="something bar other"]')
389
+ up.hello(@$buttons)
390
+ expect($target).toBeVisible()
391
+ @$barButton.prop('checked', true).change()
392
+ expect($target).toBeHidden()
393
+
394
+ it "shows the target element iff it has neither up-show-for nor up-hide-for and the selected button value is present", ->
395
+ $target = affix('.target')
396
+ up.hello(@$buttons)
397
+ expect($target).toBeHidden()
398
+ @$barButton.prop('checked', true).change()
399
+ expect($target).toBeVisible()
400
+
401
+ it "shows the target element iff its up-show-for attribute contains a value ':present' and the selected button value is present", ->
402
+ $target = affix('.target[up-show-for=":present"]')
403
+ up.hello(@$buttons)
404
+ expect($target).toBeHidden()
405
+ @$blankButton.prop('checked', true).change()
406
+ expect($target).toBeHidden()
407
+ @$barButton.prop('checked', true).change()
408
+ expect($target).toBeVisible()
409
+
410
+ it "shows the target element iff its up-show-for attribute contains a value ':blank' and the selected button value is blank", ->
411
+ $target = affix('.target[up-show-for=":blank"]')
412
+ up.hello(@$buttons)
413
+ expect($target).toBeVisible()
414
+ @$blankButton.prop('checked', true).change()
415
+ expect($target).toBeVisible()
416
+ @$barButton.prop('checked', true).change()
417
+ expect($target).toBeHidden()
418
+
419
+ it "shows the target element iff its up-show-for attribute contains a value ':checked' and any button is checked", ->
420
+ $target = affix('.target[up-show-for=":checked"]')
421
+ up.hello(@$buttons)
422
+ expect($target).toBeHidden()
423
+ @$blankButton.prop('checked', true).change()
424
+ expect($target).toBeVisible()
425
+
426
+ it "shows the target element iff its up-show-for attribute contains a value ':unchecked' and no button is checked", ->
427
+ $target = affix('.target[up-show-for=":unchecked"]')
428
+ up.hello(@$buttons)
429
+ expect($target).toBeVisible()
430
+ @$blankButton.prop('checked', true).change()
431
+ expect($target).toBeHidden()
432
+
433
+ describe 'on a text input', ->
434
+
435
+ beforeEach ->
436
+ @$textInput = affix('input[type="text"][up-toggle=".target"]')
437
+
438
+ it "shows the target element iff its up-show-for attribute contains the input value", ->
439
+ $target = affix('.target[up-show-for="something bar other"]')
440
+ up.hello(@$textInput)
441
+ expect($target).toBeHidden()
442
+ @$textInput.val('bar').change()
443
+ expect($target).toBeVisible()
444
+
445
+ it "shows the target element iff its up-hide-for attribute doesn't contain the input value", ->
446
+ $target = affix('.target[up-hide-for="something bar other"]')
447
+ up.hello(@$textInput)
448
+ expect($target).toBeVisible()
449
+ @$textInput.val('bar').change()
450
+ expect($target).toBeHidden()
451
+
452
+ it "shows the target element iff it has neither up-show-for nor up-hide-for and the input value is present", ->
453
+ $target = affix('.target')
454
+ up.hello(@$textInput)
455
+ expect($target).toBeHidden()
456
+ @$textInput.val('bar').change()
457
+ expect($target).toBeVisible()
458
+
459
+ it "shows the target element iff its up-show-for attribute contains a value ':present' and the input value is present", ->
460
+ $target = affix('.target[up-show-for=":present"]')
461
+ up.hello(@$textInput)
462
+ expect($target).toBeHidden()
463
+ @$textInput.val('bar').change()
464
+ expect($target).toBeVisible()
465
+
466
+ it "shows the target element iff its up-show-for attribute contains a value ':blank' and the input value is blank", ->
467
+ $target = affix('.target[up-show-for=":blank"]')
468
+ up.hello(@$textInput)
469
+ expect($target).toBeVisible()
470
+ @$textInput.val('bar').change()
471
+ expect($target).toBeHidden()