upjs-rails 0.17.0 → 0.18.0

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